Workflow
Unlike insert and update operations, the behavior of upsert
can vary
significantly across different vendors, if supported at all. This documentation
page provides an overview of how our implementation of upsert
works.
Details for each upsert operation can be found in the API Reference.
Searching
The general structure of a models typically consist of the following fields:
id
: a unique identifier for the model within the ChannelApe platform.external_ids
: a key-value map of external identifiers for the model, such as{"WAREHOUSE_DALLAS_ID": "12345", "SHIPPING_COMPANY_ID": "54321"}
.business_id
: the unique identifier for the business that owns the model.
When you send a model to us, we first attempt to find an existing model that matches the one you provided. The found match must be unique to be considered usable.
The matching conditions are evaluated in the following order:
id
external_ids
- Natural keys
ID
If you include the id
field, we will search for a model with that specific
identifier. Supplying the id
indicates that you already know the model you are
looking for. If it doesn't exist, we will create a new model with the supplied
id
value.
Note ids are GUIDs (globally unique identifiers) and are in the form
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
. Use a library for your platform to generate these values if you must know theid
before creating the model. It's generally not recommended to use this method.
Because id
is a globally unique identifier no other search conditions are
evaluated if. The rest of the criteria below "fall through" if they don't match.
External IDs
External IDs are a key-value map of external identifiers for the model. You only
need to provide a subset of the external IDs. For example, if your model has the
following external_ids
:
{
"KEY_1": "value_1",
"KEY_2": "value_2"
}
You can submit the following input:
{
"external_ids": {
"KEY_1": "value_1"
}
}
The model will match on KEY_1
as the values are equal. However, including
multiple keys might cause the model to not be found, for example:
{
"external_ids": {
"KEY_1": "value_1",
"KEY_2": "new_value"
}
}
Would not match as both KEY_1
and KEY_2
must match the values provided.
At the time of writing, external IDs are not supported for all models. Also no guarantee is made that external IDs are unique. The key of external id's is supposed to be the context in which it's used. For example if during inventory processing you need to reference an identifier from your warehouse management system, you could use
{"INVENTORY_PROCESSING_ID": "[ID_FROM_WAREHOUSE_MANAGEMENT_SYSTEM]"}
. This can only work as well as the id you provide is unique in the context. If the underlying system returns the same id for multiple items, upsert will likely fail to find a unique match.
Natural Keys
Natural keys are a set of fields that uniquely identify a model based on their
inherent characteristics. For example, in the context of inventory items, the
sku
or in the context of locations, the name
could be considered natural
keys. Although these values are not truly unique in most cases, they are
typically unique within a specific context.
Context Values
Any key that identifies a parent resource, also known as a foreign key, is used
to narrow down the search domain. For instance, the business_id
represents the
ID of the business that owns the model. Suppose your account has access to a
single business, and you send the following request:
{
"inventory_item": {
"sku": "sku-123",
"title": "New Title"
}
}
In this case, the same inventory item will always be found because the sku
is
unique per business. However, if your account gains access to another business
and sends the same request, there is a chance that the inventory item will be
found in the other business. This is because the sku
is not globally unique,
but unique within each business. To resolve this, include the business_id
field:
{
"inventory_item": {
"business_id": "[target_business_id]",
"sku": "sku-123",
"title": "New Title"
}
}
To identify what values constitute a context value look at the post request in
the documentation for the model you are trying to upsert. For example, the
inventory item has a
Note: This is a Foreign Key to businesses.id.
next to business_id.
In more complex models, upsert is called from the top down calling other models upsert functions. Setting context values at the top level will propagate to the nested models. For example, setting
business_id
in the top level ofupsert_shipments_full
will automatically set thebusiness_id
for addresses, locations, shipment items, and so on, when performing their respectiveupsert
operations. It's not suggested to set context values in nested models unless you specifically want to move a model from one business to another.
Inserting
If no model is found, we will create a new one. The model will be created with
the values provided in the request body. All fields marked as required must be
provided, except for id
.
Updating
If a unique model is found, we will update it with the values provided in the request body. In the update scenario all fields are optional except for identifier information discussed in search.
By default the model will be patched. This means that only the fields you
provide will be updated. If you want to replace the entire model, you can set
the patch
field to false. This will replace the entire model with the one you
send us. Do note that this will remove any fields you don't send us.
The exception to this rule is any required fields will be copied over from the existing model.
External Ids
Regardless of that patch
value, external IDs will always be patched and merged
with the existing external IDs. On key collision, the value in the request will
overwrite the existing value.
If you really want to replace the entire external IDs map, use the patch method provided by the rest api directly or mutate via the graphql api.
Result
Regardless of whether a model was inserted or updated, the response will always be the entire model as it's stored in the system. The result is identical to calling the post method on the rest api directly.
Best Practices
If you have the id
of an existing model, use it. This requires no further
context and is the most efficient way to upsert a model.
If you don't, use the external_ids
field and provide as many context
identifiers as possible. At the very minimum business_id
should be provided as
well.
In some cases natural keys are better than external IDs. By there very nature this should be apparent although efforts in documenting this by function are ongoing.
Additional Notes
Safety
Upsert is a non destructive operation, tt will never delete a model. This may
change in the future but it'll be opt-in with a delete
flag.
Upsert is also acid compliant, if the request fails, no changes will be made to the system regardless of the complexity of the model.
Permissions
Upsert has no knowledge of the current permissions of the user executing it. It also doesn't have elevated permissions. This means that it can only select, create and update models that the executing user has access to. It'll also blindly attempt to do these operations on your behalf failing the operation if you don't have the required permissions.
Summary
Always keep in mind that upsert is a best effort operation. If you provide sufficient information to uniquely identify a model the operation will generally behave as expected.