Import
ERP.net Domain API defines an Import endpoint which can be used to import multiple entities at once.
Import is unbound (not bound to any entity) action (actions are called with HTTP POST method) that inserts, updates or deletes multiple objects. Specification with example:
{
"transaction": "all-objects" | "per-object" (default),
"model": "frontend" (default) | "backend",
"objects":
[
{
"@odata.type": "Crm_Sales_Customers",
"@erpnet.action": "merge" (default) | "create" | "update" | "delete"
...
},
...
]
}
Parameters
- model: - allowed values are
commonorfrontend. This parameter indicates the data model used for the import. Front-end data model uses front-end business rules. For example front-end logic is when Quantity of a SalesOrderLine is changed the corresponding QuantityBase is calculated by a dedicated front-end business rule. Common model defines minimal business logic applicable in all cases - front-end or back-end. The default isfrontend. - transaction: -
all-objectsorper-object. This parameter defines when the changes will be commited to the database. Ifall-objectsis specified all changes are committed at once at the end of the import. Ifper-objectis specified every object is saved when it is ready. The default isper-object. - objects - an array of entity objects for import.
Properties of the objects element
- "@odata.type" - Each object must specify valid entity type. The entity type is the singular form of the entity set and can be found in the documentation for each entity. The @odata.type always starts with the default namespace
Erp.- Example Erp.General_Products_Product - "@erpnet.action" - This is an optional annotation for the desired import action. For top-level objects the default action is
create. For more information see this article. - "@erpnet.findBy" - This is an optional annotation that specifies the search criteria for the find action. For more information see this article.
- Any data property of the imported object.
Return value
Specification with example
{
"result": "success" | "fail",
"objects":
[
{
"@erpnet.result": "success" | "fail",
"@odata.id": "Crm_Sales_Customers(<guid>)",
"@erpnet.message": "<error-message>"
"@erpnet.state": "Added" | "Modified" | "Deleted" | "Unchanged"
},
...
]
}
Properties of the result value
- "@erpnet.result" -
successorfail. The result issuccessonly if all objects are imported successfully. - objects - an array of object results - one object for each imported object.
Properies of the each returned object
- "@erpnet.result" -
successorfail. - "@odata.id" - the ODATA Id of the imported object. If result is
failthis is not available. - "@erpnet.message" - the error message.
- "@erpnet.state" - the status of the imported object. One of "Added" | "Modified" | "Deleted" | "Unchanged". Indicates the operation performed for the object. Only the "@odata.id" is included in the result - no other properties.
Examples
Import Products
The following example performs merge action for General_Products_Products.
If existing product is found by the provided ExternalId it's PartNumber, BaseMeasurementCategory, MeasurementUnit, Name and ProductGroup are updated.
The action for the referenced objects is find because the included properties are only these that can be used in find criteria. BaseMeasurementCategory is searched by Name (providing @erpnet.findBy), MeasurementUnit and ProductGroup are searched by Code.
POST ~/Import
{
"model": "frontend",
"transaction": "per-object",
"objects": [
{
"@odata.type": "Erp.General_Products_Product",
"@erpnet.action": "merge",
"ExternalId": "EXT000",
"PartNumber": "DATP000",
"BaseMeasurementCategory": {
"@erpnet.action": "find",
"@erpnet.findBy": {
"Name": "Pieces"
}
},
"MeasurementUnit": {
"Code": "pcs"
},
"Name": {
"EN": "Domain API Test 000"
},
"ProductGroup": {
"Code": "DATG01",
"Name": {
"EN": "Domain API Tests"
}
}
},
{
"@odata.type": "Erp.General_Products_Product",
"@erpnet.action": "merge",
"ExternalId": "EXT001",
"PartNumber": "DATP001",
"BaseMeasurementCategory": {
"@erpnet.action": "find",
"@erpnet.findBy": {
"Name": "Pieces"
}
},
"MeasurementUnit": {
"Code": "pcs"
},
"Name": {
"EN": "Domain API Test 001"
},
"ProductGroup": {
"Code": "DATG01",
"Name": {
"EN": "Domain API Tests"
}
}
},
{
"@odata.type": "Erp.General_Products_Product",
"@erpnet.action": "merge",
"ExternalId": "EXT002",
"PartNumber": "DATP002",
"BaseMeasurementCategory": {
"@erpnet.action": "find",
"@erpnet.findBy": {
"Name": "Pieces"
}
},
"MeasurementUnit": {
"Code": "pcs"
},
"Name": {
"EN": "Domain API Test 002"
},
"ProductGroup": {
"Code": "DATG01",
"Name": {
"EN": "Domain API Tests"
}
}
}
]
}
Import Sales Order
In this example, the Import action demonstrates how you can create or update complex records without manually specifying IDs.
The key convenience is that referenced objects (like Customer, Product, or ProductGroup) can be automatically imported or updated within the same request.
This makes the Import API extremely useful for data synchronization with external systems, where you may not have direct access to internal record IDs but still need to ensure all related entities are properly linked and up to date.
The system automatically determines the @erpnet.action and @erpnet.findBy criteria based on the provided properties of the nested objects.
POST https://testdb.my.erp.net/api/domain/odata/Import
{
"objects":[
{
"@odata.type":"Erp.Crm_Sales_SalesOrder",
"DocumentType":{
"Code":"CRM_SALES_ORDER"
},
"EnterpriseCompany":{
"@erpnet.findBy":{
"Code":"714895"
}
},
"EnterpriseCompanyLocation":{
"PartyCode":"00111"
},
"Customer":{
"Number":"CST001",
"Party":{
"@odata.type":"Erp.General_Contacts_Company",
"PartyCode":"CST001",
"Name":{
"EN":"Customer 01"
},
"RegistrationType":{
"EN":"Ltd"
},
"RegistrationNumber":"001001001"
},
"EnterpriseCompany":{
"@erpnet.findBy":{
"Code":"714895"
}
}
},
"DocumentCurrency":{
"CurrencySign":"GBP"
},
"Lines":[
{
"Product":{
"PartNumber":"PRD001",
"BaseMeasurementCategory":{
"@erpnet.action":"find",
"@erpnet.findBy":{
"Name":"Pieces"
}
},
"MeasurementUnit":{
"Code":"pcs"
},
"Name":{
"EN":"Product 001"
},
"ProductGroup":{
"Code":"PGT01",
"Name":{
"EN":"Product Group 01"
}
}
},
"QuantityUnit":{
"Code":"pcs"
},
"Quantity":{
"Value":1,
"Unit":"pcs"
},
"UnitPrice":{
"Value":20,
"Currency":"GBP"
}
}
]
}
]
}
NOTE In case
@erpnet.actionannotation is missing, a default value is used. For top-level objects the default value of@erpnet.actioniscreate. For nested objects the default@erpnet.actionisfindif only properties defining the search criteria are provided (either @erpnet.findBy or data properties usable in a find action). If other data properties are provided the default@erpnet.actionismerge. See @erpnet.action topic.
Explanation of the Automatic Actions
| Property | Automatic Action | Description |
|---|---|---|
| DocumentType | find |
The system searches for a document type with Code = "CRM_SALES_ORDER". Document types are predefined, so we search by code. |
| EnterpriseCompany | find |
Since only Code is provided, the system performs a lookup for an existing Enterprise Company with that code. |
| EnterpriseCompanyLocation | find |
The field PartyCode uniquely identifies the company location, so the system searches by it. |
| Customer | merge |
Customers are matched by Number. If a customer with Number = "CST001" exists, it’s passed to the sales order. Otherwise, a new one is created. The Party subobject (Company) will also be created if missing. |
| Customer → Party | merge |
Because Party is an abstract class, the type is explicitly specified as Erp.General_Contacts_Company. A new Party (Company) record is created if none exists (The system first looks up existing party by PartyCode). |
| DocumentCurrency | find |
The CurrencySign uniquely identifies the currency (e.g., "GBP"), so an existing record is used. |
| Lines | — | Represents an array of line items that will be created as part of the sales order. |
| Product | merge |
The system searches for an existing product with PartNumber = "PRD001". If not found, a new product is created. This behavior prevents duplicate product definitions. |
| Product → BaseMeasurementCategory | find |
Uses Name = "Pieces" to locate the measurement category. Note that searching by multi-language properties is always with contains criteria. |
| Product → MeasurementUnit | find |
Looks up the measurement unit by Code = "pcs". |
| Product → ProductGroup | merge |
Uses Code = "PGT01" to find or create a product group. If it exists, it’s reused; otherwise, it’s created with the given name. |
| QuantityUnit | find |
The system searches for a measurement unit with Code = "pcs". |
| Quantity / UnitPrice | — | These are complex properties of the order line and are directly assigned, not looked up. |
Error Handling
The result contains the error message for each failed object.
Example:
{
"@erpnet.result": "fail",
"objects": [
{
"@erpnet.result": "fail",
"@erpnet.message": "Object not found: EnterpriseCompany, action: find, findBy: {\"Code\":\"546346373\"}.",
"@erpnet.error": {
"message": "Object not found: EnterpriseCompany, action: find, findBy: {\"Code\":\"546346373\"}.",
"code": 0,
"type": "InvalidOperationException",
"info": "System.InvalidOperationException: Object not found: EnterpriseCompany, action: find, findBy: {\"Code\":\"546346373\"}.\r\n at ErpNet.Model.OData.ODataResourceInfo.GetObject()\r\n at ErpNet.Model.OData.ODataResourceInfo.Execute()\r\n at ErpNet.Model.OData.ODataResourceInfo.Execute()\r\n at ErpNet.Model.OData.ODataResourceInfo.Execute()\r\n at ErpNet.Model.OData.Operations.ImportAction.HandleRequest(ODataContext odataContext, IDictionary`2 parameters, IODataRequestMessage requestMessage)",
"messageFormat": null,
"parameters": null
}
}
]
}