TypeScript and JavaScript SDK for the ChannelApe REST API
- Getting Started
- Errors
- Sessions
- Actions
- Channels
- Suppliers
- Orders
- Variants
- Businesses
- Subscriptions
- Order Activities
- Analytics
- Product Filters
- Users
- Inventories
- Locations
- Steps
- Plays
- Batches
The ChannelApe SDK is asynchronous and all functions return promises.
Create a new instance of the ChannelApeClient with your sessionId.
const channelApeClient = new ChannelApeClient({
sessionId: '4674b668-c4d2-4270-bf9b-ebaab78c378d'
});
- timeout - Number of milliseconds to wait for the API to send response headers. Defaults to 180000 (3 minutes). Cannot be set lower than 2000 (2 seconds).
- endpoint - Environment endpoint you would like to hit. Defaults to https://api.channelape.com
- logLevel - Level of logs you wish to see from the SDK. Defaults to OFF.
- maximumRequestRetryTimeout - Number of milliseconds to keep retrying a request for when an undesired response status code is received. Defaults to 180000 (3 minutes). Cannot be set lower than 2000 (2 seconds).
- minimumRequestRetryRandomDelay - Minimum number of milliseconds to randomly delay by when an undesired response status code is received. Defaults to 1000 (1 second). Cannot be set lower than 1000 (1 second).
- maximumRequestRetryRandomDelay - Maximum number of milliseconds to randomly delay by when an undesired response status code is received. Defaults to 5000 (5 seconds). Cannot be set lower than 2000 (2 seconds).
- maximumConcurrentConnections - Maximum number of connections or requests that can be made to the API at a single time. Defaults to 5.
When a call to the ChannelApe API returns an error, it can be accessed through the ApiErrors
array on the error object. The following is an example:
try {
newOrder = await client.orders().create(orderWithChannelOrderIdThatAlreadyExists);
} catch (error) {
if (error?.ApiErrors?.code === 168) {
... Handle duplicate channel order ID error
}
}
Error codes and descriptions can be found here: Postman API Error Handling
A User Account session will include your userId
which is useful when retrieving Businesses. API Account sessions will return an apiAccountId
instead.
channelApeClient.sessions().get(sessionId)
.then((session: Session) => {
// do what you need to do with session data here
// session will also include your userId or apiAccountId
});
channelapeClient.actions().get(actionId)
.then((action: Action) => {
// do what you need to do with action data here
});
channelapeClient.actions().complete(actionId)
.then((action: Action) => {
// do what you need to do with action data here
});
channelapeClient.actions().error(actionId)
.then((action: Action) => {
// do what you need to do with action data here
});
channelapeClient.actions().updateHealthCheck(actionId)
.then((action: Action) => {
// do what you need to do with action data here
});
channelapeClient.channels().get(channelId)
.then((channel: Channel) => {
// do what you need to do with channel data here
});
channelapeClient.channels().get({ businessId: 'some-valid-business-id' })
.then((channels: Channel[]) => {
// do what you need to do with channel data here
});
channelapeClient.channels().create({
additionalFields: [],
integrationId: 'some-valid-integration-id',
name: 'channel-name',
credentials: {
healthCheckInterval: 300,
payloadUrl: 'channelape.com'
},
businessId: 'some-valid-business-id',
enabled: true
})
.then((channels: Channel) => {
// do what you need to do with channel data here
});
channelapeClient.channels().update({
additionalFields: [],
id: 'valid-channel-id'
integrationId: 'some-valid-integration-id',
name: 'channel-name',
credentials: {
healthCheckInterval: 300,
payloadUrl: 'channelape.com'
},
businessId: 'some-valid-business-id',
enabled: true
})
.then((channels: Channel) => {
// do what you need to do with channel data here
});
channelapeClient.suppliers().get(supplierId)
.then((supplier: Supplier) => {
// do what you need to do with supplier data here
});
channelapeClient.suppliers().get({ businessId: 'some-valid-business-id' })
.then((suppliers: Supplier[]) => {
// do what you need to do with supplier data here
});
channelapeClient.suppliers().update(supplierUpdateRequest)
.then((supplier: Supplier) => {
// do what you need to do with supplier data here
});
channelapeClient.suppliers().create(SupplierCreateRequest)
.then((supplier: Supplier) => {
// do what you need to do with supplier data here
});
channelapeClient.orders().get(orderId)
.then((order: Order) => {
// do what you need to do with order data here
});
channelapeClient.orders().update(order)
.then((updatedOrder: Order) => {
// do what you need to do with updatedOrder data here
});
channelapeClient.orders().patch(order)
.then((patchedOrder: Order) => {
// do what you need to do with patchedOrder data here
});
const orderToCreate: OrderCreateRequest = {
additionalFields: [
{ name: 'name', value: 'CA1001' },
{ name: 'order_number', value: '1001' }
],
totalPrice: 295.99,
alphabeticCurrencyCode: 'USD',
channelId: 'your-channel-id',
channelOrderId: 'specify-your-channel-order-id',
customer: {
firstName: 'John',
lastName: 'Smith',
name: 'John Smith',
additionalFields: [
{ name: 'extraCustomerData', value: 'Put whatever you would like here' }
]
},
status: OrderStatus.OPEN,
purchasedAt: new Date(),
lineItems: [{
id: 'some-line-item-id',
quantity: 1,
sku: 'NCC1701D',
title: 'A model space ship',
additionalFields: [
{ name: 'extraLineItemData', value: 'Put whatever you would like here' }
]
}]
};
channelapeClient.orders().create(orderCreateRequest)
.then((createdOrder: Order) => {
// do what you need to do with the createdOrder data here
});
const variantsRequest: VariantsRequest = {
productId,
inventoryItemValue
};
channelApeClient.variants().get(variantsRequest)
.then((variant: Variant) => {
// do what you need to do with variant data here
});
const variantsRequestByProductId: VariantsRequestByProductId = {
productId
};
channelApeClient.variants().get(variantsRequestByProductId)
.then((variants: Variant[]) => {
// do what you need to do with variant array
})
const variantsRequest: VariantsSearchRequestByVendor = {
vendor,
businessId
};
channelApeClient.variants().search(variantsRequest)
.then((variantSearchResults: VariantSearchResults[]) => {
// do what you need to do with Variant Search Results array
});
const variantsRequest: VariantsSearchRequestByProductFilterId = {
productFilterId
};
channelApeClient.variants().search(variantsRequest)
.then((variantSearchResults: VariantSearchResults[]) => {
// do what you need to do with Variant Search Results array
});
- exactMatch parameter if set to true will only match the exact sku set on the request instead of all sku's that start with that given character sequence.
const variantsRequest: VariantsSearchRequestBySku = {
sku,
businessId,
exactMatch: true
};
channelApeClient.variants().search(variantsRequest)
.then((variantSearchResults: VariantSearchResults[]) => {
// do what you need to do with Variant Search Results array
});
- exactMatch parameter if set to true will only match the exact upc set on the request instead of all upc's that start with that given character sequence.
const variantsRequest: VariantsSearchRequestByUpc = {
upc,
businessId,
exactMatch: true
};
channelApeClient.variants().search(variantsRequest)
.then((variantSearchResults: VariantSearchResults[]) => {
// do what you need to do with Variant Search Results array
});
const variantsRequest: VariantsSearchRequestByTag = {
tag,
businessId
};
channelApeClient.variants().search(variantsRequest)
.then((variantSearchResults: VariantSearchResults[]) => {
// do what you need to do with Variant Search Results array
});
const businessesQueryRequestByBusinessId: BusinessesQueryRequestByBusinessId = {
businessId
}
channelApeClient.businesses().get(businessesQueryRequestByBusinessId)
.then((business: Business) => {
// do what you need to do with business data here
});
const businessesQueryRequestByUserId: BusinessesQueryRequestByUserId = {
userId
}
channelApeClient.businesses().get(businessesQueryRequestByUserId)
.then((businesses: Business[]) => {
// do what you need to do with businesses array data here
});
See Sessions for how to retrieve your userId
const businessMemberRequest: BusinessMemberRequest = {
userId,
businessId
}
channelApeClient.businesses().getBusinessMember(businessMemberQueryRequest)
.then((businessMember: BusinessMember) => {
// do what you need to do with the business member here
});
const businessMemberRequest: BusinessMemberRequest = {
userId,
businessId
}
channelApeClient.businesses().getBusinessUsers(businessId)
.then((users: User[]) => {
// do what you need to do with the business members here
});
const businessId = 'valid-business-id';
const email = 'valid-email-id';
channelApeClient.businesses().inviteMember(email, businessId)
then((invitationMember: InvitationResponse) => {
// send invitationMember to a user
});
const businessId = 'valid-business-id';
const userId = 'valid-id';
channelApeClient.businesses().removeMember(businessId, userId)
then((removedMember: BusinessMember) => {
// remove a member from business
});
const businessToUpdate: Business = {
name: 'name',
inventoryItemKey: InventoryItemKey.SKU,
timeZone: TimeZoneId.US_ALASKA,
alphabeticCurrencyCode: AlphabeticCurrencyCode.USD,
id: 'valid-id',
embeds: [],
errors: []
}
channelApeClient.businesses().update(businessToUpdate)
.then((updatedBusiness: Business) => {
// do what you need to do with the updated business here
});
const verificationCode = '1234567';
channelApeClient.businesses().verifyBusinessMember(verificationCode)
.then((business: Business) => {
// do what you need to do with the business here
});
const businessToCreate: BusinessCreateRequest = {
name: 'Valid Business Name',
timeZone: TimeZoneId.AMERICA_NEW_YORK,
inventoryItemKey: InventoryItemKey.SKU,
alphabeticCurrencyCode: AlphabeticCurrencyCode.USD
}
channelApeClient.businesses().create(businessToCreate)
.then((business: Business) => {
// do what you need to do with the newly created business here
});
const businessId = 'valid-business-id';
const apiAccountId = 'valid-api-account-id';
channelApeClient.businesses().apiAccounts().get(businessId, apiAccountId)
.then((apiAccount: ApiAccount) => {
// do what you need to do with the API account here
});
const businessId = 'valid-business-id';
channelApeClient.businesses().apiAccounts().get(businessId)
.then((apiAccount: ApiAccount[]) => {
// do what you need to do with the API accounts here
});
const businessId = 'valid-business-id';
const apiKeyName = 'some-api-key';
channelApeClient.businesses().apiAccounts().create(name, businessId)
.then((apiAccount: ApiAccount) => {
// do what you need to do with the API account here
});
const businessId = 'valid-business-id';
const apiAccountId = 'valid-api-account-id';
channelApeClient.businesses().apiAccounts().delete(businessId, apiAccountId)
.then((deleteAccount: ApiAccount) => {
// do what you need to do with the API account here
});
const businessId = 'valid-business-id';
channelApeClient.subscriptions().get(businessId)
.then((subscription: Subscription) => {
// do what you need to do with subscription data here
});
ChannelApe allows you to log any arbitrary action done to or information about an order through the Order Activities endpoint.
// Create an order activity if you know the channelId and channelOrderId of the order in question
const orderActivityCreateRequest: OrderActivityCreateRequestByChannel = {
channelId: 'some-channel-id',
channelOrderId: 'some-channel-order-id-belonging-to-the-specified-channel-id',
operation: OrderActivityOperation.UPDATE,
result: OrderActivityResult.SUCCESS,
messages: [
{
description: 'Arbitrary text limited to 1000 characters',
title: 'Arbitrary text limited to 100 characters.' // Order activities are grouped by title in the ChannelApe dashboard
}
]
};
// Create an order activity if you know the ChannelApe Order ID (i.e. order.id) of the order in question
const orderActivityCreateRequest: OrderActivityCreateRequestByOrderId = {
orderId: 'some-order-id',
operation: OrderActivityOperation.CREATE,
result: OrderActivityResult.ERROR,
messages: [
{
description: 'Arbitrary text limited to 1000 characters',
title: 'Arbitrary text limited to 100 characters.' // Order activities are grouped by title in the ChannelApe dashboard
}
]
};
// Create an order activity if you know the channelOrderId and the businessId of the order in question
const orderActivityCreateRequest: OrderActivityCreateRequestByBusiness = {
channelOrderId: 'some-channel-order-id',
businessId: 'some-business-id-that-the-channel-order-id-belongs-to',
operation: OrderActivityOperation.CREATE,
result: OrderActivityResult.WARN,
messages: [
{
description: 'Arbitrary text limited to 1000 characters',
title: 'Arbitrary text limited to 100 characters.' // Order activities are grouped by title in the ChannelApe dashboard
}
]
};
channelApeClient.orders().activities().create(orderActivityCreateRequest).then((orderActivity) => {
// do what you need to do with orderActivity here
});
This can only be done with an end user account that has access to ChannelApe analytics.
const embedCode = 'valid-embed-code';
const timezone = 'America/New_York';
channelApeClient.analytics().generateEmbed(embedCode, timezone)
.then((embed: Embed) => {
// Render the embed in an iframe or in a browser.
});
channelApeClient.analytics().get()
.then((reports[]: Embed) => {
// Do what you need to with list of reports
});
channelApeClient.analytics().getToken()
.then((token: Token) => {
// Do what you need to with the token
});
const productFilterRequest: ProductFilterRequest = {
businessId: 'valid-business-id'
};
channelApeClient.productFilter().create({}, productFilterRequest)
.then((productFilter: ProductFilter) => {
// Do what you need with the filter
});
const userId: string = 'some-user-id';
channelApeClient.users().get(userId)
.then((user: User) => {
// Do what you need with the user
});
const inventoryItemId: string = 'some-inventory-id';
channelApeClient.inventories().get(inventoryItemId)
.then((inventoryItem: InventoryItem) => {
// Do what you need with the inventory item
});
const inventorySku: string = 'ABC-123';
const businessId: string = '1';
channelApeClient.inventories().get(businessId, inventorySku)
.then((inventoryItems: InventoryItem[]) => {
// Do what you need with the inventory items
});
const inventoryItemCreateRequest: InventoryItemCreateRequest = {
businessId: '1',
sku: 'ABC-123',
title: 'Cool inventory title'
};
channelApeClient.inventories().create(inventoryItemCreateRequest)
.then((inventoryItem: InventoryItem) => {
// Do what you need with the created inventory item
});
const inventoryItemUpdateRequest: InventoryItemUpdateRequest = {
id: '123',
sku: 'ABC-123',
title: 'Cool inventory title'
};
channelApeClient.inventories().update(inventoryItemUpdateRequest)
.then((inventoryItem: InventoryItem) => {
// Do what you need with the updated inventory item
});
const inventoryItemId = '34';
const locationId = '28';
const quantity = 31;
const inventoryStatus = InventoryStatus.AVAILABLE_TO_SELL;
const adjustmentRequest: AdjustmentRequest = {
inventoryItemId,
locationId,
quantity,
inventoryStatus
};
channelApeClient.inventories().quantities().adjust(adjustmentRequest)
.then((adjustment: Adjustment) => {
// Do what you need with the adjustment
});
- idempotentKey is optional. It will default to a UUID if nothing is provided. This key is used to determine if an adjustment should be created or if it is a duplicate.
const inventoryItemId = '34';
const locationId = '28';
const quantity = -148;
const inventoryStatus = InventoryStatus.ON_ORDER;
const idempotentKey = `${new Date().toISOString()}_${locationId}_${inventoryItemId}_${inventoryStatus}`;
const adjustmentRequest: AdjustmentRequest = {
inventoryItemId,
locationId,
quantity,
inventoryStatus,
idempotentKey
};
channelApeClient.inventories().quantities().set(adjustmentRequest)
.then((adjustment: Adjustment) => {
// Do what you need with the adjustment
});
- If at least one adjustment fails, the call will wait for all other pending requests to complete and then throw an error.
- The deduplication key specifies a string which will be used to determine if an adjustment should be created. If a key with the same string for a particular inventory item, location, and status was already used, it will not perform the adjustment. For example, this can be used to allow only one adjustment per day so that if the batched adjustments are sent through a second time, only the ones which failed will be recreated. Here is the generated key format: {deduplicationKey}_{locationId}_{inventoryItemId}_{status}
Currently Allowed Inventory Statuses:
- AVAILABLE_TO_SELL
- ON_HOLD
- ON_HAND
- COMMITTED
- ON_ORDER
const adjustmentsBySku: AdjustmentsBySku = [{
sku: 'A1',
adjustments: [{
quantity: 1,
inventoryStatus: InventoryStatus.AVAILABLE_TO_SELL,
deduplicationKey: '05052020',
locationId: '123',
memo: 'Memo'
}, {
quantity: 3,
inventoryStatus: InventoryStatus.ON_HOLD,
deduplicationKey: '05052020',
locationId: '123',
memo: 'Memo'
}]
}, {
sku: 'B1',
adjustments: [{
quantity: 2,
inventoryStatus: InventoryStatus.AVAILABLE_TO_SELL,
deduplicationKey: '05052020',
locationId: '123',
memo: 'Memo'
}, {
quantity: 0,
inventoryStatus: InventoryStatus.ON_HOLD,
deduplicationKey: '05052020',
locationId: '123',
memo: 'Memo'
}]
}];
channelApeClient.inventories().quantities().setBatch(adjustmentsBySku)
.then(() => {
// All adjustments completed successfully
});
channelApeClient.inventories().quantities().adjustBatch(adjustmentsBySku)
.then(() => {
// All adjustments completed successfully
});
const inventoryItemId = '35';
channelApeClient.inventories().quantities().retrieve(inventoryItemId)
.then((quantities: InventoryItemQuantity[]) => {
// Do what you need with the inventory item quantities
});
const locationId: string = 'some-location-id';
channelApeClient.locations().get(locationId)
.then((location: Location) => {
// Do what you need with the location
});
const businessId: string = '1';
channelApeClient.locations().getByBusinessId(businessId)
.then((location: Location) => {
// Do what you need with the locations
});
const locationCreationRequest: LocationCreateRequest = {
businessId: '1',
name: 'Some location Name'
};
channelApeClient.locations().create(locationCreationRequest)
.then((location: Location) => {
// Do what you need with the created location
});
const locationUpdateRequest: LocationUpdateRequest = {
id: '123',
name: 'Some updated location Name'
};
channelApeClient.locations().update(locationUpdateRequest)
.then((location: Location) => {
// Do what you need with the updated location
});
const locationId: string = '1';
channelApeClient.locations().getSLA(locationId)
.then((locationSLA: LocationSLA) => {
// Do what you need with the locations
});
const LocationSLAUpdate: LocationSLA = {
createdAt: '2018-04-24T14:02:34.703Z',
fulfillmentSLAHours: '1',
locationId: '1',
operatingDays: [
{
createdAt: '2018-04-24T14:02:34.703Z',
day: 'T',
end: '10:00',
fulfillmentCutoffTime: '09:30',
id: '23',
open: '08:00',
updatedAt: '2018-04-24T14:02:34.703Z'
},
{
createdAt: '2018-04-24T14:02:34.703Z',
day: 'W',
end: '10:00',
fulfillmentCutoffTime: '09:50',
id: '24',
open: '08:00',
updatedAt: '2018-04-24T14:02:34.703Z'
}
],
updatedAt: '2018-04-24T14:02:34.703Z'
};
const locationId: string = '1';
channelApeClient.locations().updateSla(locationId, sla)
.then((locationSLA: LocationSLA) => {
// Do what you need with the locations update
});
const locationId: string = '1';
channelApeClient.locations().getClosures(locationId)
.then((locationClosures: LocationClosedDay[]) => {
// Do what you need with the locations
});
const closesDates: LocationClosureRequest = {
closedDays: [
'2021/02/01',
'2021/03/01',
'2021/04/01',
'2021/05/01',
'2021/06/01'
]
};
const locationId: string = '1';
channelApeClient.locations().updateClosures(locationId, closedDates)
.then((locationClosures: LocationClosedDay[]) => {
// Do what you need with the locations closures update
});
channelapeClient.steps().get(stepId)
.then((step: Step) => {
// do what you need to do with step data here
});
channelapeClient.plays().get(playId)
.then((play: Play) => {
// do what you need to do with play data here
});
channelapeClient.plays().get()
.then((plays: Play[]) => {
// do what you need to do with all play data here
});
channelapeClient.recipes().getAll(businessId)
.then((recipes: Recipe[]) => {
//do what you need to do with all recipes data here
});
channelapeClient.recipes().get(recipeId)
.then((recipes: Recipe) => {
//do what you need to do with the recipe data here
});
channelapeClient.schedules().getAll(businessId)
.then((schedules: Schedule[]) => {
// do what you need to do with all schedules data here
});
channelapeClient.schedules().get(scheduleId)
.then((schedules: Schedule) => {
// do what you need to do with the schedule data here
});
channelapeClient.batches().get(batchId)
.then((batch: Batch) => {
// do what you need to do with the batch data here
});
You can trigger a new batch by following the format below. The max number of adjustments per batch is 5000.
const batchRequest = BatchAdjustmentCreationRequest = {
businessId: '91f47fdf-fd71-484c-9b3b-db4e2877a229',
adjustments: [
{
locationId: '47',
operation: AdjustmentType.ADJUST,
sku: 'ABC-123',
memo: 'Adjusting for intraday inventory.',
quantity: 1,
inventoryStatus: InventoryStatus.AVAILABLE_TO_SELL,
idempotentKey: 'some-idempotent-key'
},
{
locationId: '12',
operation: AdjustmentType.SET,
inventoryItemId: 123,
memo: 'Adjusting for nightly true up.',
quantity: 1,
inventoryStatus: InventoryStatus.COMMITTED,
idempotentKey: 'some-idempotent-key'
},
],
}
channelapeClient.batches().createInventoryAdjustmentBatch(batchRequest)
.then((batchResponse: BatchResponse) => {
// do what you need to do with the batch response here
});
You can make adjustments with an expirationTime
,
just provide an expirationTime
and we'll stage an adjustment with the quantity inversed, -15 in this example, for that specific time:
const batchRequest = BatchAdjustmentCreationRequest = {
businessId: '91f47fdf-fd71-484c-9b3b-db4e2877a229',
adjustments: [
{
locationId: '47',
operation: AdjustmentType.ADJUST,
sku: 'ABC-123',
memo: 'Reserving for reason xyz.',
quantity: 15,
expirationTime: new Date('2023-12-20T12:30:00Z'),
inventoryStatus: InventoryStatus.RESERVE,
idempotentKey: 'some-idempotent-key'
},
],
}
You can also make reserve adjustments based on a percentage amount by switching out quantity
for futureAppliedAtpPercentage
and adding an expirationTime
:
const batchRequest = BatchAdjustmentCreationRequest = {
businessId: '91f47fdf-fd71-484c-9b3b-db4e2877a229',
adjustments: [
{
locationId: '47',
operation: AdjustmentType.ADJUST,
sku: 'ABC-123',
memo: 'Reserving for reason xyz.',
futureAppliedAtpPercentage: 15,
expirationTime: new Date('2023-12-20T12:30:00Z'),
inventoryStatus: InventoryStatus.RESERVE,
idempotentKey: 'some-idempotent-key'
},
],
}