ERP → Salesforce¶
ERP is the client. Salesforce is the server. Auth is the OAuth client credentials described under How the systems connect, against /services/data/v67.0. If ERP has no instance URL and no token, the local rows are still saved and these calls are not made.
Saving a territory or a geography does not call Salesforce. POST /api/territories/{code}/sync-to-salesforce does. Sync of TER-KA-BLR-01 first upserts TER-KA and TER-KA-BLR when those rows are not already Synced.
POST /api/territories¶
Region¶
ERP answers HTTP 200. syncStatus is Pending. children is an empty array.
Zone and Territory¶
{
"territoryCode": "TER-KA-BLR",
"name": "Bengaluru",
"territoryLevel": "Zone",
"parentTerritoryCode": "TER-KA"
}
{
"territoryCode": "TER-KA-BLR-01",
"name": "Bengaluru Central",
"territoryLevel": "Territory",
"parentTerritoryCode": "TER-KA-BLR"
}
The same POST updates a code that already exists. Changing the level is refused when that code already has children or geographies.
| HTTP | error |
message |
|---|---|---|
| 400 | invalid_json |
invalid request body |
| 400 | validation_error |
territory code is required |
| 400 | validation_error |
name is required |
| 400 | validation_error |
invalid territory level |
| 400 | validation_error |
invalid territory status |
| 400 | validation_error |
region must not have a parent territory |
| 400 | validation_error |
parent territory code is required for Zone level |
| 400 | validation_error |
parent territory code is required for Territory level |
| 400 | validation_error |
parent territory TER-KA not found |
| 400 | validation_error |
parent territory TER-KA must be Zone level, got Region |
| 400 | validation_error |
cannot change level of TER-KA-BLR-01: it has 1 child territories |
| 400 | validation_error |
cannot change level of TER-KA-BLR-01: it has 1 geographies |
Setting "status": "Inactive" is allowed. The Salesforce body then sends "Is_Active__c": false.
POST /api/territories/TER-KA-BLR-01/geographies¶
Only Territory level. The three sample rows:
Each call answers HTTP 200 with the geography, syncStatus Pending. A second post of the same type and value is accepted and sets that row back to Pending. The id in that second response is new. The stored row keeps the original id.
| HTTP | error |
message |
|---|---|---|
| 400 | validation_error |
geographies can only be assigned to Territory-level records |
| 400 | validation_error |
invalid geography type |
| 400 | validation_error |
geography value is required |
| 400 | validation_error |
pincode must be exactly 6 digits |
| 404 | not_found |
territory not found |
DELETE /api/territories/TER-KA-BLR-01/geographies/{id} removes a row that has never been synced. Success is HTTP 204 with an empty body. A Synced row is HTTP 409:
{
"error": "conflict",
"message": "geography is already synced to Salesforce and cannot be removed"
}
There is no Salesforce delete for Territory_Geography__c.
PATCH Territory_Master__c by code¶
One call per level. The code is in the URL, not the body.
PATCH /services/data/v67.0/sobjects/Territory_Master__c/Territory_Code__c/TER-KA
Authorization: Bearer {access token}
Content-Type: application/json
Region. No parent.
Territory. The parent link is the Zone code.
{
"Territory_Name__c": "Bengaluru Central",
"Territory_Level__c": "Territory",
"Parent_Territory__r": {
"Territory_Code__c": "TER-KA-BLR"
},
"Is_Active__c": true
}
| Salesforce field | Example | Where ERP got it |
|---|---|---|
Territory_Code__c |
TER-KA-BLR-01 |
the URL. External id. Not in the body |
Territory_Name__c |
Bengaluru Central |
name |
Territory_Level__c |
Territory |
territoryLevel |
Parent_Territory__r.Territory_Code__c |
TER-KA-BLR |
parentTerritoryCode. Omitted when blank |
Is_Active__c |
true |
true when status is Active, false when Inactive |
A first create is HTTP 201:
ERP checks that id is present and does not store it. An update of a code Salesforce already has is HTTP 204 with an empty body. ERP then treats the call as a failure, because the id is missing: territory upsert: empty id (status 204). The local row becomes Failed. See Salesforce → ERP.
The outbound log reference is territoryCode=TER-KA-BLR-01 name="Bengaluru Central" level=Territory parent=TER-KA-BLR.
When the master and its geographies both succeed, the sync API answers HTTP 200:
A Salesforce error on this call is HTTP 500 internal_error with message internal server error. The local syncStatus is Failed.
POST /composite/sobjects¶
After the master upsert, ERP loads geographies whose sync status is Pending or Failed. It queries Salesforce first and skips a type and value that already exist:
GET /services/data/v67.0/query?q=SELECT Geography_Type__c, Geography_Value__c FROM Territory_Geography__c WHERE Territory__r.Territory_Code__c='TER-KA-BLR-01'
The rows that are missing are created together. allOrNone is false. At most 200 records.
{
"allOrNone": false,
"records": [
{
"attributes": { "type": "Territory_Geography__c" },
"Territory__r": { "Territory_Code__c": "TER-KA-BLR-01" },
"Geography_Type__c": "State",
"Geography_Value__c": "Karnataka"
},
{
"attributes": { "type": "Territory_Geography__c" },
"Territory__r": { "Territory_Code__c": "TER-KA-BLR-01" },
"Geography_Type__c": "City",
"Geography_Value__c": "Bengaluru"
},
{
"attributes": { "type": "Territory_Geography__c" },
"Territory__r": { "Territory_Code__c": "TER-KA-BLR-01" },
"Geography_Type__c": "Pincode",
"Geography_Value__c": "560025"
}
]
}
Salesforce answers HTTP 200 with one result per record:
[
{ "id": "a0G000000000001AAA", "success": true, "errors": [], "created": true },
{ "id": "a0G000000000002AAA", "success": true, "errors": [], "created": true },
{ "id": "a0G000000000003AAA", "success": true, "errors": [], "created": true }
]
ERP does not store those ids. If any result has success: false, every geography in that batch is marked Failed in ERP, including ones Salesforce did create. The next sync queries again and skips the ones that exist.
A geography log line is territoryCode=TER-KA-BLR-01 type=Pincode value=560025.
Failed Integration Log¶
When the master upsert or the geography create fails, ERP also posts an Integration_Log__c so Salesforce can alert an admin. Log_Type__c is Inbound on that object. A failed repeat of TER-KA-BLR-01 looks like this. Operation__c is Deactivate when the territory status is Inactive. Otherwise it is Create or Update.
{
"Log_Type__c": "Inbound",
"Operation__c": "Update",
"Status__c": "Failed",
"ERP_Code__c": "TER-KA-BLR-01",
"Error_Message__c": "territory upsert: empty id (status 204)",
"Timestamp__c": "2026-11-06T05:00:00.000Z",
"Payload_Reference__c": "territoryCode=TER-KA-BLR-01 name=\"Bengaluru Central\" level=Territory parent=TER-KA-BLR"
}
POST /api/territories/sync-unsynced runs that same sync for every territory, ordered Region, then Zone, then Territory. The name says unsynced. The query returns every row, including ones already marked Synced, so a second pass hits the empty 204 and counts those as failed. It then syncs geographies that are still Pending or Failed. The answer is HTTP 200, for example { "synced": 3, "failed": 0 }.