This library helps make Epicor calls from an Node application
npm install epicor-rest-nodenpm i --save epicor-rest-node
bash
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.
$3
#### Setup
`typescript
import { EpicorRestService, EpicorRestVersion, EpicorError, EpicorResponse } from 'epicor-rest-node';
import { EpicorLicenseType } from 'epicor-rest-node/dist/models/EpicorLicenseType';
import { EpicorResponseType } from 'epicor-rest-node/dist/models/EpicorType';
`
`typescript
// 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.ResponseType = EpicorResponseType.FULL; //Defaults to FULL. Use SIMPLE for direct data returns
EpicorRest.PreserveSession = true; //Defaults to true. Set to false to create new session per request
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)
`typescript
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("Ice.Lib.SessionModSvc", "SetEmployee", { employeeID: empID });
if (EpicorError.isError(switchRes)) {
console.log( Error switching employee: ${switchRes.message});
return false;
}
return switchRes;
}
.
.
.
}
`
#### Call BO Methods
`typescript
let params = new Map();
params.set('$filter','ABCCode1 eq \'A\'');
// SIMPLE Response Type (default in v1.x, opt-in for v2.x)
// Returns data directly, errors are returned (not thrown)
EpicorRest.ResponseType = EpicorResponseType.SIMPLE;
const simpleResult = await EpicorRest.BoGet('Erp.BO.ABCCodeSvc','ABCCodes',params);
if (EpicorError.isError(simpleResult)) {
// Handle error - error is returned
console.log( Error ${simpleResult.status}: ${simpleResult.message});
} else {
// Handle success - direct data access
console.log('Data:', simpleResult);
}
// FULL Response Type (default in v2.x, recommended)
// Returns EpicorResponse wrapper with status, message, data, and headers
// Errors are thrown (not returned)
EpicorRest.ResponseType = EpicorResponseType.FULL;
try {
const fullResult = await EpicorRest.BoGet('Erp.BO.ABCCodeSvc','ABCCodes',params);
// fullResult is EpicorResponse with status, message, data, headers
console.log( Status: ${fullResult.status} - ${fullResult.message});
console.log('Data:', fullResult.data);
console.log('Headers:', fullResult.headers);
} catch (error) {
// Handle error - error is thrown in FULL mode
if (EpicorError.isError(error)) {
console.log(Error ${error.status}: ${error.message});
}
}
// POST example with FULL mode (using try/catch)
try {
const postResult = await EpicorRest.BoPost('Erp.BO.ABCCodeSvc','ABCCodes',data);
console.log('Success:', postResult.data);
} catch (error) {
if (EpicorError.isError(error)) {
console.log( Error ${error.status}: ${error.message});
}
}
// Patch and Delete are also available with the same pattern
`
#### Call BAQ
`typescript
let params = new Map();
params.set('$top','13');
// SIMPLE Mode - errors are returned
EpicorRest.ResponseType = EpicorResponseType.SIMPLE;
const baqResult = await EpicorRest.BaqGet('zCustomer01', params);
if (EpicorError.isError(baqResult)) {
console.log( Error ${baqResult.status}: ${baqResult.message});
} else {
console.log('BAQ Data:', baqResult);
}
// FULL Mode - errors are thrown
EpicorRest.ResponseType = EpicorResponseType.FULL;
try {
const baqResult = await EpicorRest.BaqGet('zCustomer01', params);
console.log('BAQ Data:', baqResult.data);
console.log('Response Status:', baqResult.status);
} catch (error) {
if (EpicorError.isError(error)) {
console.log( Error ${error.status}: ${error.message});
}
}
//BAQ Patch is also available with the same pattern
`
#### Get BAQ Metadata
`typescript
// Get BAQ metadata/schema information
const metaResult = await EpicorRest.BaqMetaData('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
`typescript
// Get OpenAPI 3.0.1 swagger documentation for a specific BAQ
const swaggerResult = await EpicorRest.GetBaqSwagger('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
`typescript
let smsSend =
{
ToPhone:'123456789',
ToMsg:'Zup from Node'
};
`
`typescript
// SIMPLE Mode - errors are returned
EpicorRest.ResponseType = EpicorResponseType.SIMPLE;
const efxResult = await EpicorRest.EfxPost('FacilityPaging','SendSMS',smsSend);
if (EpicorError.isError(efxResult)) {
console.log( Error ${efxResult.status}: ${efxResult.message});
} else {
console.log('EFX Response:', efxResult);
}
// FULL Mode - errors are thrown
EpicorRest.ResponseType = EpicorResponseType.FULL;
try {
const efxResult = await EpicorRest.EfxPost('FacilityPaging','SendSMS',smsSend);
console.log('EFX Response:', efxResult.data);
console.log('Status:', efxResult.status);
} catch (error) {
if (EpicorError.isError(error)) {
console.log( Error ${error.status}: ${error.message});
}
}
`
#### Get Function Library List
`typescript
// 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
`typescript
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
`typescript
// 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
Version 2.0.0 introduces two distinct error handling modes based on the ResponseType setting:
##### Response Types
SIMPLE Mode (EpicorResponseType.SIMPLE)
- Returns data directly (no wrapper)
- Errors are returned as EpicorError objects (not thrown)
- Use EpicorError.isError() to check for errors
- Backward compatible with v1.x behavior
FULL Mode (EpicorResponseType.FULL) - Default in v2.0.0
- Returns EpicorResponse wrapper with status, message, data, and headers
- Errors are thrown as EpicorError objects (not returned)
- Use try/catch blocks for error handling
- Provides additional response metadata
##### Error Handling in SIMPLE Mode
`typescript
import { EpicorError, EpicorResponseType } from 'epicor-rest-node';
EpicorRest.ResponseType = EpicorResponseType.SIMPLE;
// Errors are RETURNED in SIMPLE mode
const result = await EpicorRest.BoGet('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 T
console.log('Success:', result);
}
// Alternative: instanceof check
if (result instanceof EpicorError) {
console.log(Error: ${result.message});
} else {
console.log('Success:', result);
}
`
##### Error Handling in FULL Mode
`typescript
import { EpicorError, EpicorResponse, EpicorResponseType } from 'epicor-rest-node';
EpicorRest.ResponseType = EpicorResponseType.FULL; // Default in v2.0.0
// Errors are THROWN in FULL mode - use try/catch
try {
const result = await EpicorRest.BoGet('Erp.BO.ABCCodeSvc','ABCCodes', params);
// result is EpicorResponse
console.log( HTTP Status: ${result.status});
console.log(Message: ${result.message});
console.log('Data:', result.data);
console.log('Headers:', result.headers);
} catch (error) {
// Error is thrown and caught here
if (EpicorError.isError(error)) {
console.log(Error ${error.status}: ${error.message});
console.log('Raw Error:', error.error);
} else {
// Handle unexpected errors
console.log('Unexpected error:', error);
}
}
`
##### EpicorError Properties
- status: number - HTTP status code
- message: string - Human-readable error message
- error?: any - Raw error object from the server (optional)
##### EpicorResponse Properties (FULL mode only)
- status: number - HTTP status code (200, 201, etc.)
- message: string - HTTP status text or custom message
- data: T - The response data of type T
- headers: any - Response headers from the server
##### Common Error Scenarios
`typescript
// SIMPLE Mode - Check returned value
EpicorRest.ResponseType = EpicorResponseType.SIMPLE;
const result = await EpicorRest.BoGet('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});
}
}
// FULL Mode - Use try/catch
EpicorRest.ResponseType = EpicorResponseType.FULL;
try {
const result = await EpicorRest.BoGet('NonExistent.BO','Method', params);
console.log('Success:', result.data);
} catch (error) {
if (EpicorError.isError(error)) {
switch (error.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: ${error.message});
}
}
}
`
##### Choosing Between SIMPLE and FULL Mode
Use SIMPLE mode when:
- Migrating from v1.x and want to minimize code changes
- Prefer explicit error checks with if statements
- Don't need response metadata (headers, status messages)
- Want errors as return values rather than exceptions
Use FULL mode when:
- Starting a new project
- Want comprehensive response information (status, message, headers)
- Prefer try/catch error handling (more idiomatic JavaScript/TypeScript)
- Need to distinguish between successful responses with different status codes
- Want access to response headers without additional parameters
#### 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.
`typescript
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();
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
$3
#### 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.
`typescript
// CreateSession returns EpicorRestSession | null
const session = await EpicorRest.CreateSession();
if (session) {
try {
console.log(Session created: ${session.SessionId});
console.log(User: ${session.UserId});
// Any calls made in here will use the above created session
let params = new Map();
params.set('$filter','ABCCode1 eq \'A\'');
// Using FULL mode (default)
try {
const result = await EpicorRest.BoGet('Erp.BO.ABCCodeSvc','ABCCodes',params);
console.log('Data:', result.data);
} catch (error) {
if (EpicorError.isError(error)) {
console.log( Error ${error.status}: ${error.message});
}
}
} 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:
`typescript
// 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.
`typescript
{
"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.
`typescript
/**
* Post EFX Data
*/
@Post('PostEFX/:library/:method')
async postEfx(
@Param('company') company: string,
@Param('library') library: string,
@Param('method') method: string,
@Body() body: Record, // 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.
`typescript
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 {
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(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
`typescript
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 {
// to 'EpicorResponse'
public async callEFX(user: any, library: string, functionName: string, params: any, callContext: string | undefined = undefined): Promise {
``