ERP → Salesforce¶
ERP is the client. Salesforce is the server. Auth is the same OAuth client credentials as onboarding, against /services/data/v67.0. If ERP has no instance URL and no token, the local row is still saved and these calls are not made.
POST /api/prices¶
TEA-500 must already exist. This is the first price for that product in INR, so nothing is closed.
{
"price": {
"productCode": "TEA-500",
"batch": "B2026-11",
"currency": "INR",
"price": 240,
"effectiveFrom": "2026-11-01T00:00:00Z"
}
}
ERP builds skuBatchKey as TEA-500_B2026-11, saves the row with no end date, upserts Salesforce, then answers HTTP 201. On a successful push the body includes "skuBatchKey": "TEA-500_B2026-11", "syncStatus": "Synced", and "active": true. A failed push still returns 201, with "syncStatus": "Failed".
| HTTP | error |
message |
|---|---|---|
| 400 | invalid_json |
invalid request body |
| 400 | validation_error |
effective to must be blank when creating a price |
| 400 | validation_error |
sku batch key is required |
| 400 | validation_error |
product code is required |
| 400 | validation_error |
batch is required |
| 400 | validation_error |
price must be greater than 0 |
| 400 | validation_error |
effective from is required |
| 400 | validation_error |
product not found |
| 400 | validation_error |
a price already exists for batch B2026-11 of this product; use a new batch number |
| 400 | validation_error |
effective from must be after the current active price effective from |
PATCH Product_Price__c by key¶
The key is in the URL, not the body. Effective_To__c is omitted while the price is open.
PATCH /services/data/v67.0/sobjects/Product_Price__c/SKU_Batch_Key__c/TEA-500_B2026-11
Authorization: Bearer {access token}
Content-Type: application/json
{
"Product_Master__r": { "Product_Code__c": "TEA-500" },
"Batch_Number__c": "B2026-11",
"Unit_Price__c": 240,
"CurrencyIsoCode": "INR",
"Effective_From__c": "2026-11-01"
}
| Salesforce field | Example | Where ERP got it |
|---|---|---|
SKU_Batch_Key__c |
TEA-500_B2026-11 |
the URL. External id. {productCode}_{batch}, upper case |
Product_Master__r.Product_Code__c |
TEA-500 |
productCode |
Batch_Number__c |
B2026-11 |
batch |
Unit_Price__c |
240 |
price |
CurrencyIsoCode |
INR |
currency. A blank currency is sent as INR |
Effective_From__c |
2026-11-01 |
effectiveFrom, as a UTC date |
Effective_To__c |
omitted | effectiveTo. Omitted when the price is open |
A first create is HTTP 201 with an id. ERP checks that the id is present and does not store it. An update of a key Salesforce already has is HTTP 204 with an empty body. ERP treats the empty body as a failure: price upsert: empty id (status 204). The local row becomes Failed. See Salesforce → ERP.
The log reference is skuBatchKey=TEA-500_B2026-11 productCode=TEA-500 batch=B2026-11 price=240.00 INR currentActive=true.
POST /api/prices/TEA-500_B2026-11/sync-to-salesforce sends the same PATCH for the open INR row. Add ?currency=INR to name the currency. Add ?id= to name one stored row. Success is HTTP 200:
A Salesforce error on this retry is HTTP 500 internal_error with message internal server error. A missing row is HTTP 404 not_found with message product price not found.
POST /api/prices/sync-unsynced runs the single PATCH for every price, ordered by key, including rows already marked Synced and rows that were closed. It does not send the Composite transition. A second pass hits the empty 204 and counts those rows as failed. The answer is HTTP 200, for example { "synced": 1, "failed": 0 }.
A new batch¶
B2026-12 starts on 1 December. The November price is still open, so ERP closes it and sends both rows in one Composite call.
{
"price": {
"productCode": "TEA-500",
"batch": "B2026-12",
"currency": "INR",
"price": 255,
"effectiveFrom": "2026-12-01T00:00:00Z"
}
}
ERP answers HTTP 201 with the December price. skuBatchKey is TEA-500_B2026-12. The November row stays in ERP with effectiveTo 2026-11-30. Both rows share the sync result: Synced when the Composite succeeds, Failed when it does not.
The date written locally and the date sent to Salesforce are different. Locally the November price ends on 2026-11-30, the day before the new price starts, so only one price is active on a calendar day. The Composite body end-dates it on 2026-12-01, the new effective from.
{
"allOrNone": true,
"compositeRequest": [
{
"method": "PATCH",
"url": "/services/data/v67.0/sobjects/Product_Price__c/SKU_Batch_Key__c/TEA-500_B2026-11",
"referenceId": "endDateOldBatch0",
"body": { "Effective_To__c": "2026-12-01" }
},
{
"method": "PATCH",
"url": "/services/data/v67.0/sobjects/Product_Price__c/SKU_Batch_Key__c/TEA-500_B2026-12",
"referenceId": "newBatch",
"body": {
"Product_Master__r": { "Product_Code__c": "TEA-500" },
"Batch_Number__c": "B2026-12",
"Unit_Price__c": 255,
"CurrencyIsoCode": "INR",
"Effective_From__c": "2026-12-01"
}
}
]
}
Every open price for TEA-500 in INR is an endDateOldBatch step, numbered from 0. The new price is always newBatch. allOrNone is true, so a failed step rolls the Composite back. A later single sync of the November row sends Effective_To__c 2026-11-30, the date stored locally.
PATCH /api/prices/TEA-500_B2026-12¶
The URL is the key. The body has no price wrapper. Product code, batch, and currency must stay the same. This updates the open row for that key and currency. ?id= selects one stored row instead.
{
"productCode": "TEA-500",
"batch": "B2026-12",
"currency": "INR",
"price": 260,
"effectiveFrom": "2026-12-01T00:00:00Z"
}
ERP answers HTTP 200 and sends the single PATCH, with "Unit_Price__c": 260. Because Salesforce already has TEA-500_B2026-12, that PATCH is HTTP 204 with an empty body, and the local row becomes Failed.
| HTTP | error |
message |
|---|---|---|
| 400 | validation_error |
product code and batch cannot be changed; create a new price instead |
| 400 | validation_error |
currency cannot be changed; create a new price instead |
| 400 | validation_error |
effective to must be on or after effective from |
| 404 | not_found |
product price not found |
Failed Integration Log¶
A failed price upsert or a failed Composite also posts Integration_Log__c. Log_Type__c is Inbound. Operation__c is Create on the first push and on a new batch, and Update on a PATCH. ERP_Code__c is the sku batch key of the new price.
{
"Log_Type__c": "Inbound",
"Operation__c": "Update",
"Status__c": "Failed",
"ERP_Code__c": "TEA-500_B2026-12",
"Error_Message__c": "price upsert: empty id (status 204)",
"Timestamp__c": "2026-11-06T05:30:00.000Z",
"Payload_Reference__c": "skuBatchKey=TEA-500_B2026-12 productCode=TEA-500 batch=B2026-12 price=260.00 INR currentActive=false"
}