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 🙏

© 2025 – Pkg Stats / Ryan Hefner

epicor-rest-node

v1.3.0

Published

This library helps make Epicor calls from an Node application

Readme

Epicor Rest Helper for Node

This library helps make Epicor calls from a Node application. This library is not official and has no direct relationship with Epicor (c). It is merely a helper library maintained by the community.

Table of Contents


Installation

npm i --save epicor-rest-node

Testing

This library includes a comprehensive test suite to verify functionality and help prevent regressions. See TESTING.md for detailed setup and usage instructions.

Quick Start:

# Copy environment template
cp .env.example .env

# Edit .env with your Epicor server details
# Then run tests
npm test

The test suite validates all library functionality including authentication, Business Object calls, BAQ queries, Epicor Functions, error handling, and more.

Usage

Setup

import { EpicorRestService, EpicorRestVersion, EpicorError } from 'epicor-rest-node';
import { EpicorLicenseType } from 'epicor-rest-node/dist/models/EpicorLicenseType';
  // For single instance creation
  let EpicorRest = new EpicorRestService();

  // or injected into another class/service such as a controller
  constructor(private readonly epicorSvc: EpicorService) {}

  // Instance properties required to function
  EpicorRest.AppPoolHost = 'subdomain.domain.tld';
  EpicorRest.AppPoolInstance = 'Epicor10Instance';
  EpicorRest.UserName = 'MyEpicorUserName';
  EpicorRest.Password = 'MyEpicorPassword';
  EpicorRest.APIKey = 'xxxxxxxxxxxxxxxxxxxxxxxxx'; //Needed for V2
  EpicorRest.Company = 'EPIC01';
  EpicorRest.EpicorRestVersion = EpicorRestVersion.V2; //Defaults to V2
  EpicorRest.License = EpicorLicenseType.WebService; //Defaults to Default
  EpicorRest.EfxAttemptStagingRetry = false; //Defaults to false. Set to true only during development to retry EFX calls with staging endpoint
Handling Instance Scope

@Injectable({ scope: Scope.REQUEST }) tells Nest to treat your service class as a provider with request scope. In practical terms:

  1. @Injectable(): Marks the class so Nest can manage and inject it wherever needed (dependency injection).
  2. scope: Scope.REQUEST: Tells Nest to create a new instance of this class for each incoming request, rather than reusing a single global instance. This is useful if each request needs its own state or dependencies. (NOTE: This will also consume additional licenses in Epicor)
import { EpicorRestService } from 'epicor-rest-node';

// Adding REQUEST scope will force a new instance per request made vs a singleton
@Injectable({scope: Scope.REQUEST})
export class EpicorService extends EpicorRestService implements OnModuleInit {
  public Plant: string = 'MfgSys';

  /**
   * Constructor
   */
  constructor(
    private readonly config: ConfigService
  ) {
    super();
    this.AppPoolHost = 'subdomain.domain.tld';
    this.AppPoolInstance = 'Epicor10Instance';
    this.UserName = 'MyEpicorUserName';
    this.Password = 'MyEpicorPassword';
    this.APIKey = 'xxxxxxxxxxxxxxxxxxxxxxxxx'; //Needed for V2
    this.Company = 'EPIC01';
    this.EpicorRestVersion = EpicorRestVersion.V2; //Defaults to V2
    this.License = EpicorLicenseType.WebService; //Defaults to Default

    // TODO: Do we need to implement timezone offset
    this.CallSettings = new CallSettings(this.Company, this.Plant, '', '', '');

    console.log('My Custom EpicorService Constructor');
  }
  .
  .
  .
  /**
  * Switch Employee ID
  * @param empID
  */
  public async switchEmployee(empID: string) {
    let switchRes = await this.BoPost<any>("Ice.Lib.SessionModSvc", "SetEmployee", { employeeID: empID });
    if (EpicorError.isError(switchRes)) {
      console.log(`Error switching employee: ${switchRes.message}`);
      return false;
    }
    return switchRes;
  }
  .
  .
  .
}

Call BO Methods

let params = new Map<string,string>();
params.set('$filter','ABCCode1 eq \'A\'');

// Using async/await with proper error handling
const result = await EpicorRest.BoGet<any>('Erp.BO.ABCCodeSvc','ABCCodes',params);
if (EpicorError.isError(result)) {
  // Handle error
  console.log(`Error ${result.status}: ${result.message}`);
} else {
  // Handle success
  console.log(result);
}

// POST example with async/await
const postResult = await EpicorRest.BoPost<any>('Erp.BO.ABCCodeSvc','ABCCodes',data);
if (EpicorError.isError(postResult)) {
  console.log(`Error ${postResult.status}: ${postResult.message}`);
} else {
  console.log(postResult);
}

// Or using traditional promise chain (still works)
EpicorRest.BoGet('Erp.BO.ABCCodeSvc','ABCCodes',params).then(res => {
  if (EpicorError.isError(res)) {
    console.log(`Error ${res.status}: ${res.message}`);
  } else {
    console.log(res);
  }
});

// Patch and Delete are also available with the same pattern

Call BAQ

let params = new Map<string,string>();
params.set('$top','13');

// Using async/await with proper error handling
const baqResult = await EpicorRest.BaqGet<any>('zCustomer01', params);
if (EpicorError.isError(baqResult)) {
  // Handle error
  console.log(`Error ${baqResult.status}: ${baqResult.message}`);
} else {
  // Handle success
  console.log(baqResult);
}

// Or using traditional promise chain (still works)
EpicorRest.BaqGet('zCustomer01', params).then(res => {
  if (EpicorError.isError(res)) {
    console.log(`Error ${res.status}: ${res.message}`);
  } else {
    console.log(res);
  }
});

//BAQ Patch is also available with the same pattern

Get BAQ Metadata

// Get BAQ metadata/schema information
const metaResult = await EpicorRest.BaqMetaData<any>('zCustomer01');
if (EpicorError.isError(metaResult)) {
  // Handle error
  console.log(`Error ${metaResult.status}: ${metaResult.message}`);
} else {
  // Handle success - metaResult contains OData $metadata
  console.log('BAQ Metadata:', metaResult);
  // Use metadata to understand available fields, data types, relationships
}


// Or using traditional promise chain (still works)
EpicorRest.BaqMetaData('zCustomer01').then(res => {
  if (EpicorError.isError(res)) {
    console.log(`Error ${res.status}: ${res.message}`);
  } else {
    console.log('Metadata:', res);
  }
});

Use Cases for BAQ Metadata:

  • Getting schema information about BAQ fields and data types
  • Understanding relationships between tables in the BAQ
  • Building dynamic queries based on available fields
  • OData service discovery for client applications
  • Validating field names before making BAQ queries

Get BAQ Swagger Documentation

// Get OpenAPI 3.0.1 swagger documentation for a specific BAQ
const swaggerResult = await EpicorRest.GetBaqSwagger<any>('zCustomer01');
if (EpicorError.isError(swaggerResult)) {
  // Handle error
  console.log(`Error ${swaggerResult.status}: ${swaggerResult.message}`);
} else {
  // Handle success - swaggerResult contains the full OpenAPI specification
  console.log('BAQ Info:', swaggerResult.info.title, swaggerResult.info.description);
  console.log('Server URL:', swaggerResult.servers[0]?.url);
  
  // Explore available endpoints
  console.log('Available Endpoints:');
  Object.keys(swaggerResult.paths).forEach(path => {
    const pathInfo = swaggerResult.paths[path];
    Object.keys(pathInfo).forEach(method => {
      const operation = pathInfo[method];
      console.log(`  ${method.toUpperCase()} ${path}: ${operation.summary}`);
    });
  });
  
  // Get schema information for return data
  if (swaggerResult.components?.schemas) {
    console.log('Available Schemas:');
    Object.keys(swaggerResult.components.schemas).forEach(schemaName => {
      const schema = swaggerResult.components.schemas[schemaName];
      console.log(`  ${schemaName}:`);
      if (schema.properties) {
        Object.keys(schema.properties).forEach(propName => {
          const prop = schema.properties[propName];
          console.log(`    - ${propName}: ${prop.type} ${prop.description ? `(${prop.description})` : ''}`);
        });
      }
    });
  }
  
  // Extract query item schema for understanding return structure
  const queryItemSchema = swaggerResult.components?.schemas?.['Epicor.QueryItem'];
  if (queryItemSchema?.properties) {
    console.log('BAQ Return Fields:');
    Object.keys(queryItemSchema.properties).forEach(fieldName => {
      const field = queryItemSchema.properties[fieldName];
      console.log(`  ${fieldName}: ${field.type} ${field.required ? '(required)' : '(optional)'}`);
      if (field.description) {
        console.log(`    Description: ${field.description}`);
      }
    });
  }
}

// Or using traditional promise chain (still works)
EpicorRest.GetBaqSwagger('zCustomer01').then(res => {
  if (EpicorError.isError(res)) {
    console.log(`Error ${res.status}: ${res.message}`);
  } else {
    console.log('BAQ Swagger Documentation:', res);
  }
});

Use Cases for BAQ Swagger Documentation:

  • Understanding available BAQ endpoints (/Data, /$metadata, etc.)
  • Getting complete schema information about BAQ return types
  • Discovering field descriptions and data types for BAQ results
  • Building dynamic UIs based on BAQ field definitions
  • API documentation and client code generation
  • Validating BAQ capabilities and available operations
  • Understanding OData query parameters supported by the BAQ
  • Integration planning and data mapping

Call Epicor Function

let smsSend = 
  {
    ToPhone:'123456789',
    ToMsg:'Zup from Node'
  };
// Using async/await with proper error handling
const efxResult = await EpicorRest.EfxPost<any>('FacilityPaging','SendSMS',smsSend);
if (EpicorError.isError(efxResult)) {
  // Handle error
  console.log(`Error ${efxResult.status}: ${efxResult.message}`);
} else {
  // Handle success
  console.log(efxResult);
}

// Or using traditional promise chain (still works)
EpicorRest.EfxPost('FacilityPaging','SendSMS',smsSend).then(res => {
  if (EpicorError.isError(res)) {
    console.log(`Error ${res.status}: ${res.message}`);
  } else {
    console.log(res);
  }
});

Get Function Library List

// Get list of available EFX function libraries
const libraryResult = await EpicorRest.GetFunctionLibraryList();
if (EpicorError.isError(libraryResult)) {
  // Handle error
  console.log(`Error ${libraryResult.status}: ${libraryResult.message}`);
} else {
  // Handle success - libraryResult is an array of function libraries
  console.log('Available Function Libraries:');
  libraryResult.forEach(library => {
    console.log(`- ${library.LibraryId}: ${library.Description || 'No description'}`);
  });
}

// Or using traditional promise chain (still works)
EpicorRest.GetFunctionLibraryList().then(res => {
  if (EpicorError.isError(res)) {
    console.log(`Error ${res.status}: ${res.message}`);
  } else {
    res.forEach(lib => {
      console.log(`Library: ${lib.LibraryId}`);
    });
  }
});

Use Cases for Function Library List:

  • Discovering available EFX function libraries in your Epicor environment
  • Building dynamic UIs that show available functions to users
  • Validating library names before making EFX function calls
  • Documentation and API exploration
  • Integration planning and capability assessment

Get Function Library Specification

import { EpicorFunctionSpecParser } from 'epicor-rest-node';

// Get detailed OpenAPI specification for a specific function library
const specResult = await EpicorRest.GetFunctionLibrarySpec('CustomerPortal');
if (EpicorError.isError(specResult)) {
  // Handle error
  console.log(`Error ${specResult.status}: ${specResult.message}`);
} else {
  // Handle success - specResult is the full OpenAPI 3.0.1 specification
  console.log('Library Info:', specResult.info.title, specResult.info.version);
  
  // Use the parser to extract function information
  const parser = new EpicorFunctionSpecParser(specResult);
  const functions = parser.getFunctions();
  
  console.log('Available Functions:');
  functions.forEach(func => {
    console.log(`- ${func.functionName} (${func.method}): ${func.summary}`);
    console.log(`  Requires Input: ${func.requiresInput}`);
    
    // Show input parameters
    if (func.inputSchema) {
      console.log('  Input Parameters:');
      func.inputSchema.forEach(param => {
        console.log(`    - ${param.name}: ${param.type} ${param.required ? '(required)' : '(optional)'}`);
      });
    }
    
    // Generate input template
    const inputTemplate = parser.generateInputTemplate(func.functionName);
    console.log('  Input Template:', JSON.stringify(inputTemplate, null, 2));
  });
}

// Get specific function information
const loginFunc = parser.getFunction('Login');
if (loginFunc) {
  console.log('Login Function Details:', loginFunc);
  
  // Generate a typed input object for the Login function
  const loginInput = parser.generateInputTemplate('Login');
  // loginInput will be: { UserName: '', Password: '' }
  
  // Now you can populate and use it with EfxPost
  loginInput.UserName = 'testuser';
  loginInput.Password = 'testpass';
  
  const result = await EpicorRest.EfxPost('CustomerPortal', 'Login', loginInput);
}

Use Cases for Function Library Specification:

  • Understanding function signatures and parameter types
  • Generating typed input objects for function calls
  • Building dynamic forms based on function parameters
  • API documentation and code generation
  • Validating function inputs before making calls
  • Creating SDKs or wrappers for specific function libraries

Get Environment Information

// Get Epicor environment details
const envResult = await EpicorRest.GetEnvironment();
if (EpicorError.isError(envResult)) {
  console.log(`Error getting environment: ${envResult.message}`);
} else {
  console.log('Environment info:', envResult);
  // envResult contains environment details (which company the user has access to and which plants). No Api Required
  // See Exported Class EpicorEnvironment
}

Error Handling

All API methods return either the expected data type T or an EpicorError object. The library provides several ways to handle errors:

Using EpicorError.isError() Type Guard
import { EpicorError } from 'epicor-rest-node';

const result = await EpicorRest.BoGet<any>('Erp.BO.ABCCodeSvc','ABCCodes', params);
if (EpicorError.isError(result)) {
  // TypeScript knows this is an EpicorError
  console.log(`HTTP Status: ${result.status}`);
  console.log(`Error Message: ${result.message}`);
  console.log(`Raw Error:`, result.error);
} else {
  // TypeScript knows this is your expected data type
  console.log('Success:', result);
}
Using instanceof Check
const result = await EpicorRest.BoGet<any>('Erp.BO.ABCCodeSvc','ABCCodes', params);
if (result instanceof EpicorError) {
  console.log(`Error: ${result.message}`);
} else {
  console.log('Success:', result);
}
EpicorError Properties
  • status: number - HTTP status code
  • message: string - Human-readable error message
  • error?: any - Raw error object from the server (optional)
Common Error Scenarios
// Network/Connection errors typically have status 500
// Epicor authentication errors typically have status 401
// Not found errors typically have status 404
// Validation errors typically have status 400

const result = await EpicorRest.BoGet<any>('NonExistent.BO','Method', params);
if (EpicorError.isError(result)) {
  switch (result.status) {
    case 401:
      console.log('Authentication failed - check credentials');
      break;
    case 404:
      console.log('Business Object or method not found');
      break;
    case 500:
      console.log('Server error or network issue');
      break;
    default:
      console.log(`Unexpected error: ${result.message}`);
  }
}

Capturing Response Headers

All API methods support capturing HTTP response headers using the CapturedResponseHeaders class. This is useful for accessing server information, debugging, or extracting custom headers returned by Epicor.

import { CapturedResponseHeaders } from 'epicor-rest-node';

// Create a CapturedResponseHeaders instance
const capturedHeaders = new CapturedResponseHeaders();

// Pass it as the last parameter to any API method
const result = await EpicorRest.GetEnvironment(null, null, capturedHeaders);

if (!EpicorError.isError(result)) {
  // Access all captured headers
  console.log('All headers:', capturedHeaders.headers);
  
  // Get specific headers (case-insensitive)
  const contentType = capturedHeaders.getHeader('content-type');
  const server = capturedHeaders.getHeader('server');
  
  console.log('Content-Type:', contentType);
  console.log('Server:', server);
  
  // Check if a header exists
  if (capturedHeaders.hasHeader('x-powered-by')) {
    console.log('X-Powered-By:', capturedHeaders.getHeader('x-powered-by'));
  }
  
  // Get all header names
  const headerNames = capturedHeaders.getHeaderNames();
  console.log('Available headers:', headerNames);
}

// Works with all API methods
const params = new Map<string, string>();
params.set('$top', '5');

const boHeaders = new CapturedResponseHeaders();
const boResult = await EpicorRest.BoGet('Erp.BO.Customer', 'GetList', params, null, null, boHeaders);

const baqHeaders = new CapturedResponseHeaders();
const baqResult = await EpicorRest.BaqGet('MyBAQ', params, null, null, baqHeaders);

const efxHeaders = new CapturedResponseHeaders();
const efxResult = await EpicorRest.EfxPost('MyLibrary', 'MyFunction', {}, false, null, null, efxHeaders);

Use Cases for Captured Response Headers:

  • Debugging API issues by examining server response headers
  • Accessing rate limiting information or quotas
  • Extracting custom Epicor headers with business logic information
  • Monitoring server performance metrics
  • Implementing caching strategies based on cache-control headers
  • Security auditing and compliance logging

Advanced Usage

Epicor Session

An Epicor session can be established at any point by invoking EpicorRest.CreateSession() and make sure to kill the session when you are done.

Note: Createsession() (lowercase 's') is deprecated. Use CreateSession() (capital 'S') instead.

  const sessionCreated = await EpicorRest.CreateSession();
  if (sessionCreated) {
    try {
      // Any calls made in here will use the above created session
      let params = new Map<string,string>();
      params.set('$filter','ABCCode1 eq \'A\'');

      const result = await EpicorRest.BoGet<any>('Erp.BO.ABCCodeSvc','ABCCodes',params);
      if (EpicorError.isError(result)) {
        console.log(`Error ${result.status}: ${result.message}`);
      } else {
        console.log(result);
      }
    } finally {
      await EpicorRest.DestroySession();
    }
  } else {
    console.log('Failed to create session');
  }

An Epicor session can be killed manually by invoking EpicorRest.DestroySession() this needs to be done after the last call to the BO/BAQ/EFX etc.

Session Management Methods

Once a session is created, you can manage session context using these methods:

// Set the current employee for the session
const employeeSet = await EpicorRest.SetEmployee('EMP123');
if (employeeSet) {
  console.log('Employee context updated');
}

// Set the current plant/site for the session
const plantSet = await EpicorRest.SetPlant('MfgSys');
if (plantSet) {
  console.log('Plant context updated');
}

// Set the current workstation for the session
const workstationSet = await EpicorRest.SetWorkstation('WKST001');
if (workstationSet) {
  console.log('Workstation context updated');
}

// Set client data for the session (sync client information)
const clientDataSet = await EpicorRest.SetClientData(
  'john.doe',              // clientUserName
  'DESKTOP-ABC123',        // clientComputerName
  'M/d/yyyy',              // clientDateFormat (optional, defaults to 'M/d/yyyy')
  undefined,               // appserver (optional, defaults to current instance URL)
  0                        // clientTerminalID (optional, defaults to 0)
);
if (clientDataSet) {
  console.log('Client data synchronized');
}

// Get current session information
const sessionInfo = await EpicorRest.GetSessionInfo();
console.log('Session Info:', sessionInfo);
// Returns session details including user, company, plant, employee, version info, etc.

// Get theme and user options information
const themeInfo = await EpicorRest.GetThemeInfo();
console.log('Theme Info:', themeInfo);
// Returns user preferences, theme settings, and shell layout options

Important: All session management methods (SetEmployee, SetPlant, SetWorkstation, SetClientData) require an active session created with CreateSession(). They will throw an error if called without a valid session.

Use Cases:

  • SetEmployee: Switch context to a different employee for labor tracking or permissions
  • SetPlant: Change the manufacturing site context for operations
  • SetWorkstation: Set the workstation for production or quality operations
  • SetClientData: Synchronize client machine information with the server session
  • GetSessionInfo: Retrieve current session state, user info, and Epicor version
  • GetThemeInfo: Get user preferences and theme settings for UI customization

Epicor Call Context

Sending Call Context To Node

Managing call context as an object can be done by using the EpicorRest CallContext models.

Generate a header with the call context values you want in your client application and send it in the contextheader header of your request.

  {
    "Context":{
      "BpmData":[
        {
          "Character01":"FOO",
          "Character02":"BAR",
          "Checkbox01":true,
          "Date01":"2024-12-27"
        }
      ]
    }
  }
Handling Request Call Context

Below is an example controller endpoint that grabs the contextheader from the request headers and sends it to our EpicorService that implements the EpicorRestNode module. It also then takes the call context and sends it back to our client that made the request.

  /**
 * Post EFX Data
 */
  @Post('PostEFX/:library/:method')
  async postEfx(
    @Param('company') company: string,
    @Param('library') library: string,
    @Param('method') method: string,
    @Body() body: Record<string, any>, // Capture the entire JSON body
    @Req() req,
    @Res({ passthrough: true }) res: any, // passthrough is important to allow us to send the context headers back but allow interceptors to still run on our 'data' return if we need to
  ) {
    // Pass the body directly as params
    const { data, context } = await this.epicorSvc.callEFX(company, library, method, body, req.headers['contextheader']);

    // Set the `callcontext` in the response headers to our client
    res.setHeader('contextheader', JSON.stringify(context) || '');

    // Send response
    return data;
  }

In our actual service we send the headers to our EpicorRestNode module in the method signature.

  import { BpmData, CallContext, Client, Context } from 'epicor-rest-node/dist/models/CallContext';
  import { EpicorError } from 'epicor-rest-node';

  public async callEFX(user: any, library: string, functionName: string, params: any, callContext: string | undefined = undefined): Promise<any> {
    let efxData = undefined;
    let respCallContext = undefined;

    const sessionCreated = await EpicorRest.Createsession();
    if (sessionCreated) {
      try {
        // Convert the string passed in to an object of CallContext
        const reqCallContext = new CallContext(JSON.parse(callContext).Context);

        // Modify the call context as you need      
        reqCallContext.Context.BpmData[0].Character01 = "MyCustomAppSentThis";
    
        // Call our method in the EpicorRestNode module passing CallContext as a param
        const efxResult = await this.EfxPost<any>(library, functionName, params, false, reqCallContext);
        
        if (EpicorError.isError(efxResult)) {
          console.log(`EFX Error ${efxResult.status}: ${efxResult.message}`);
          efxData = null;
        } else {
          efxData = efxResult;
          // Note: For call context response headers, you'll need to modify HttpJSON to return headers
          // respCallContext = new CallContext(JSON.parse(res.headers['contextheader']).Context);
        }
      } finally {
        await EpicorRest.DestroySession();
      }
    } else {
      console.log('Failed to create session');
    }

    // Here we return our response data and the call context as two seperate objects to our controller above
    return { data: efxData, context: respCallContext };
  }

Additionally if you wanted to you could create an interface for that return data so in your controller you can get the type cast autocomplete goodness

export interface EpicorResponse {
  data: any;
  context: CallContext;
}

// In our service we switch our promise from 'any'
public async callEFX(user: any, library: string, functionName: string, params: any, callContext: string | undefined = undefined): Promise<any> {

// to 'EpicorResponse'
public async callEFX(user: any, library: string, functionName: string, params: any, callContext: string | undefined = undefined): Promise<EpicorResponse> {