Salesforce Integration
Resolve Salesforce store records, link them to KLIQ locations, and push distribution-check facings back to Salesforce — all in a single SDK flow.
Per-tenant org configuration
The Salesforce integration runs against a per-tenant configured org. Each tenant may point to a sandbox or a production Salesforce org, mirroring the platform's SalesforcePush.env field. No credentials are required in the SDK — the API uses the org wired up in the platform admin panel for your tenant.
Overview
The Salesforce integration exposes three operations on tenant.salesforce:
| Method | Description |
|---|---|
getStore(storeIdOrName, by?) | Fetch a Salesforce store record together with its product list |
link(params) | Bind a Salesforce store to a KLIQ location and map individual products to tracked objects |
push(params) | Send the latest CV-detected facings for a location back to Salesforce |
The typical flow is: resolve a store → link it to a KLIQ location → push facings after each observation cycle.
Endpoints
/v1/salesforce/stores/{storeIdOrName}Fetch a Salesforce store and its product list/v1/salesforce/linkLink a Salesforce store to a KLIQ location with SKU mappings/v1/salesforce/pushPush distribution-check facings to SalesforceStep 1 — Resolve a Salesforce store
Use getStore to look up a store by its Salesforce ID (default) or by name.
// Look up by Salesforce store ID (default)
const store = await tenant.salesforce.getStore('0019000001XyZaA', 'id');
console.log(store.storeId); // "0019000001XyZaA"
console.log(store.storeName); // "Delhaize Flagey"
console.log(store.accountId); // "0019000000AbCdE"
// Each product the store carries in Salesforce
store.storeProducts.forEach((p) => {
console.log(p.storeProductId); // "01t9000000XyZ01"
console.log(p.productId); // "01t9000000AbCd0"
console.log(p.productName); // "Duvel 330ml 6-pack"
console.log(p.locationName); // "Beverage aisle"
console.log(p.kliqFacings); // 4 (last pushed value, or undefined)
console.log(p.kliqUpdateTime); // "2026-07-20T09:15:00Z" (or undefined)
});
// Look up by store name instead
const byName = await tenant.salesforce.getStore('Delhaize Flagey', 'name');SalesforceStoreDto fields
| Field | Type | Description |
|---|---|---|
storeId | string | Salesforce store (Account) ID |
storeName | string | Human-readable store name |
accountId | string | Parent Salesforce Account ID |
storeProducts | SalesforceStoreProductDto[] | Products listed for this store in SF |
SalesforceStoreProductDto fields
| Field | Type | Description |
|---|---|---|
storeProductId | string | Salesforce StoreProduct record ID |
productId | string | Salesforce Product2 ID |
productName | string | Product display name |
locationName | string | undefined | Store location/aisle label in SF |
kliqFacings | number | undefined | Last facing count pushed by KLIQ |
kliqUpdateTime | string | undefined | ISO timestamp of last KLIQ push |
Step 2 — Link a store to a KLIQ location
link binds a Salesforce store to a KLIQ location and maps individual Salesforce products to KLIQ tracked objects. This only needs to be done once per store (or whenever the product set changes).
const { locationId, linkedSkus } = await tenant.salesforce.link({
locationId: 'loc_abc123',
salesforceStoreId: '0019000001XyZaA',
skuLinks: [
{
trackedObjectId: 'obj_duvel_330',
salesforceProductId: '01t9000000AbCd0',
},
{
trackedObjectId: 'obj_duvel_750',
salesforceProductId: '01t9000000AbCd1',
},
],
});
console.log(locationId); // "loc_abc123"
console.log(linkedSkus); // 2 — number of SKU pairs savedSalesforceLinkParams fields
| Field | Type | Description |
|---|---|---|
locationId | string | KLIQ location ID to bind |
salesforceStoreId | string | Salesforce store ID to associate |
skuLinks | Array<{ trackedObjectId: string; salesforceProductId: string }> | Product-to-SKU mappings |
Step 3 — Push facings to Salesforce
After observations are captured and processed, call push to write the latest CV-detected facing counts back to the linked Salesforce store.
const result = await tenant.salesforce.push({
locationId: 'loc_abc123',
source: 'observation', // 'admin' | 'observation' — defaults to 'admin'
});
console.log(result.pushId); // "push_xyz789"
console.log(result.status); // "ok" | "error"
console.log(result.productsSent); // 2 — SKUs successfully written to SF
console.log(result.productsSkipped); // 1 — SKUs that could not be written
// Inspect why individual SKUs were skipped
result.skipped.forEach((s) => {
console.log(s.trackedObjectId); // "obj_duvel_750"
console.log(s.reason); // "no_salesforce_product_link"
});SalesforcePushParams fields
| Field | Type | Description |
|---|---|---|
locationId | string | KLIQ location whose facings to push |
source | 'admin' | 'observation' | undefined | Push trigger context (optional) |
SalesforcePushResult fields
| Field | Type | Description |
|---|---|---|
pushId | string | Unique identifier for this push event |
status | 'ok' | 'error' | Overall push result |
productsSent | number | Number of SKUs successfully written |
productsSkipped | number | Number of SKUs that could not be written |
skipped | Array<{ trackedObjectId: string; reason: string }> | Per-SKU skip details |
End-to-end example
Resolve a Salesforce store by name, link it to an existing KLIQ location, and push facings immediately.
// 1. Resolve the store by name
const store = await tenant.salesforce.getStore('Delhaize Flagey', 'name');
// 2. Link the store to a KLIQ location with product mappings
const { locationId, linkedSkus } = await tenant.salesforce.link({
locationId: 'loc_abc123',
salesforceStoreId: store.storeId,
skuLinks: store.storeProducts.map((p, i) => ({
trackedObjectId: `obj_product_${i}`,
salesforceProductId: p.productId,
})),
});
console.log(`Linked ${linkedSkus} SKUs to location ${locationId}`);
// 3. Push the latest observed facings to Salesforce
const result = await tenant.salesforce.push({
locationId,
source: 'observation',
});
if (result.status === 'ok') {
console.log(`Pushed ${result.productsSent} products to Salesforce`);
} else {
console.warn(`Push finished with errors — ${result.productsSkipped} skipped`);
result.skipped.forEach((s) => console.warn(s.trackedObjectId, s.reason));
}Next steps
- Missions & Routes — Salesforce-pulled routes link back to the stores resolved here
- Observations — Capture the shelf photos that feed the facing counts
- CV Jobs — Understand how facing counts are produced from observations
- Webhooks — Get notified when a push completes or errors