API Extensions
Extend the behavior of an API with your business logic
The commercetools platform provides default data structures and default behavior that is useful for many of the customers. However, each project has its unique requirements. Similar to data structures that can be customized with Custom Types and Product Types, additional behavior can be added. For behavior to be executed within a short timeframe, Subscriptions can be used. For behavior that needs to be executed before the API call succeeds, API Extensions can be used.
Good use cases for API Extensions are: Validating the content of a cart (for example no more than 8 crates of beverages can be ordered at once), calculating custom shipping costs, or adding mandatory items, like insurance, to a cart.
An API Extension gets called after processing of a create or update request of an API call, but before the result is persisted. The API Extension can validate the object, or apply additional updates to it.
API Extensions are called remotely from the commercetools platform. You may host them as you wish, but we provide integrations with serverless functions.
Please note that an API Extension affects the performance of the API it is extending: If it fails, the whole API call fails. If it takes a second to return a result, the whole API call takes a second longer. You should prefer to use a Subscription over an API Extension when you can react to an event asynchronously.
An API Extension is applied to API calls from all clients, including those provided by commercetools, like the Merchant Center.
API Extensions are currently available for carts
, orders
, payments
, and customers
, but will be rolled out to other endpoints during the beta phase.
The first part of this document explains how to setup an extension, while the second part details the input and the possible responses of the extension. The third part details the limits and error cases related to extensions. You may also be interested in the tutorial on API Extensions.
Representations
Extension
id
- Stringkey
- String - Optional - User-specific unique identifier for the extensionversion
- NumbercreatedAt
- DateTimecreatedBy
- CreatedBy BETA
Present on resources created after 2019-02-01 except for events not tracked.lastModifiedAt
- DateTimelastModifiedBy
- LastModifiedBy BETA
Present on resources updated after 2019-02-01 except for events not tracked.destination
- Destination - Details where the extension can be reachedtriggers
- Array of Trigger - Describes what triggers the extensiontimeoutInMs
- Number - Optional The maximum time the commercetools platform waits for a response from the extension. If no timeout is provided, the default of 2000 (2 seconds) is used for all types of extensions. The maximum value is 10000 ms (10 seconds) forpayment
extensions and 2000 ms (2 seconds) for all other extensions.
ExtensionDraft
key
- String - Optional
User-specific unique identifier for the extensiondestination
- Destination
Details where the extension can be reachedtriggers
- Array of Trigger
Describes what triggers the extensiontimeoutInMs
- Number - Optional
The maximum time the commercetools platform waits for a response from the extension. If no timeout is provided, the default of 2000 (2 seconds) is used for all types of extensions. The maximum value is 10000 ms (10 seconds) forpayment
extensions and 2000 ms (2 seconds) for all other extensions. This limit can be increased per project after we review the performance impact. Please contact Support via the Support Portal and provide the region, project key and use case.
Destination
A destination contains all info necessary for the commercetools platform to call the extension. Destinations can be differentiated by the type
field.
We believe that deploying an extension on a Function-as-a-Service is a good fit. Azure Functions and Google Cloud Functions can be called with the HTTP
Destination, with special support for authenticating Azure Functions.
When retrieving a Destination, secrets, or access keys are partially hidden for security reasons.
HTTP Destination
type
- String -"HTTP"
url
- Stringauthentication
- HttpDestinationAuthentication - Optional
An encrypted https
connection is strongly recommended for production setups, but we accept unencrypted http
connections for development purposes.
HTTP redirects will not be followed. Cache headers will be ignored.
AWS Lambda Destination
type
- String -"AWSLambda"
arn
- String
The Amazon Resource Name (ARN) of the Lambda function, in the formatarn:aws:lambda:<region>:<accountid>:function:<functionName>
.accessKey
- StringaccessSecret
- String - Partially hidden on retrieval
It is recommended to create an Identify and Access Management (IAM) user with an accessKey
and accessSecret
pair specifically for each API Extension that only has the lambda:InvokeFunction
permission on this function.
Please note that AWS Lambda limits the size of the payload to 6 MB (this limit also applies if the Lambda function is invoked by the API Gateway). You should not use AWS Lambda if you anticipate that your API Extensions will receive JSON input exceeding 6 MB.
HTTP Destination Authentication
Azure Functions Authentication
type
- String -"AzureFunctions"
key
- String - Partially hidden on retrieval
See the Azure Functions documentation. To protect your Azure Function, set its authLevel
to function
and provide the functions key here. The commercetools platform will set the x-functions-key
header.
To protect the secret key from being exposed, please remove the code parameter and the secret key from the URL, for example do not use:
https://foo.azurewebsites.net/api/bar?code=secret
URL, but only:
https://foo.azurewebsites.net/api/bar
.
Trigger
resourceTypeId
- String - Currently,cart
,order
,payment
, andcustomer
are supported.actions
- Array of String - Currently,Create
andUpdate
are supported.
Get an Extension
Get an Extension by ID
Retrieves the representation of an extension by its id
.
Endpoint: /{projectKey}/extensions/{id}
Method: GET
OAuth 2.0 Scopes: manage_extensions:{projectKey}
Response Representation: Extension
Get an Extension by Key
Retrieves the representation of an extension by its key.
Endpoint: /{projectKey}/extensions/key={key}
Method: GET
OAuth 2.0 Scopes: manage_extensions:{projectKey}
Response Representation: Extension
Query Extensions
Endpoint: /{projectKey}/extensions
Method: GET
OAuth 2.0 Scopes: manage_extensions:{projectKey}
Response Representation: PagedQueryResult with results
containing an array of Extension
Query Parameters:
Create an Extension
Endpoint: /{projectKey}/extensions
Method: POST
OAuth 2.0 Scopes: manage_extensions:{projectKey}
Request Representation: ExtensionDraft
Response Representation: Extension
The creation of an Extension is eventually consistent, it may take up to a minute before it becomes fully active.
Currently, a maximum of 25 Extensions can be created per project.
Update Extension
Updates of an Extension are eventually consistent, it may take up to a minute before changes becomes fully active.
Update Extension by ID
Endpoint: /{projectKey}/extensions/{id}
Method: POST
OAuth 2.0 Scopes: manage_extensions:{projectKey}
Response Representation: Extension
Fields:
version
- Number - Required
The expected version of the extension on which the changes should be applied. If the expected version does not match the actual version, a 409 Conflict will be returned.actions
- Array of UpdateAction - Required
The list of update actions to be performed on the extension.
Update Extension by Key
Endpoint: /{projectKey}/extensions/key={key}
Method: POST
OAuth 2.0 Scopes: manage_extensions:{projectKey}
Response Representation: Extension
Fields:
version
- Number - Required
The expected version of the extension on which the changes should be applied. If the expected version does not match the actual version, a 409 Conflict will be returned.actions
- Array of UpdateAction - Required
The list of update actions to be performed on the extension.
Update actions
Set Key
action
- String -"setKey"
key
- String - Optional
Ifkey
is absent ornull
, the existing key, if any, will be removed.
Change Triggers
action
- String -"changeTriggers"
triggers
- Array of Trigger
Change Destination
action
- String -"changeDestination"
destination
- Destination
Set TimeoutInMs
action
- String -"setTimeoutInMs"
timeoutInMs
- Number - Optional
The maximum time the commercetools platform waits for a response from the extension. If no timeout is provided, the default of 2000 (2 seconds) is used for all types of extensions. The maximum value is 10000 ms (10 seconds) forpayment
extensions and 2000 ms (2 seconds) for all other extensions. This limit can be increased per project after we review the performance impact. Please contact Support via the Support Portal and provide the region, project key and use case.
Delete Extension
The deletion of an Extension is eventually consistent, it may take up to a minute before it becomes fully deactived.
Delete Extension by ID
Endpoint: /{projectKey}/extensions/{id}
Method: DELETE
OAuth 2.0 Scopes: manage_extensions:{projectKey}
Query Parameters:
version
- Number - Required
Delete Extension by Key
Endpoint: /{projectKey}/extensions/key={key}
Method: DELETE
OAuth 2.0 Scopes: manage_extensions:{projectKey}
Query Parameters:
version
- Number - Required
The API Extension within the flow of the API call
The API Extensions are called during the execution of a create or update request. The commercetools platform first validates that the requested operation can be done, and applies the operation. For example, if an API call requests to add a product to a cart, the commercetools platform validates that the product exists and is sold in the country of the cart, and then adds it to the cart. However, the result (for example the cart) is not persisted yet. It is forwarded to the API Extensions, who can run their business logic. Three ways for an API Extension exist to properly respond:
- If the Extension finds the result to be valid, and does not want to perform any changes, it can easily return a success. The commercetools platform will persist the result.
- If the Extension finds that the result is not valid (for example the particular customer may not purchase this product), it can return a list of errors. The commercetools platform will not persist the result, and return the errors to the original caller of the API.
- If the Extension wants to perform additional changes, it can return a list of updates (for example if it wants to add a mandatory insurance to the cart, it can return an
AddLineItem
update). The commercetools platform will try to perform the updates. Should that fail, the original caller of the API will receive the errors. Otherwise, the original caller will receive the final result (for example the cart including the mandatory insurance).
If an API Extension fails to respond properly
If an API Extension fails to respond properly, the whole API call will fail. If the API Extension did not return a result in time or could not be reached, the original API caller will receive a 504
Gateway Timeout
HTTP status code. If the API Extension did return a response, but it could not be parsed properly, the original API caller will receive a 502
Bad Gateway
HTTP status code.
In both cases, additional information on the cause of the failure is returned to the original API client.
Please note that in terms of Service Level Agreement (SLA), a failure caused by an API Extension does not count as a failure of the commercetools platform. For example if an API Extension for the carts is down and causes downtime for your shop, the SLA won't cover that.
Multiple API Extensions in a single API call
If multiple API Extensions are triggered by an API call, they will be called in parallel. Their responses will be merged, but without a guaranteed order (for example if two extensions each return an error, their order in the error list is undefined. If two extensions return updates, the order in which the updates are performed is undefined).
Responses are merged based on their priority/severity:
- A failure to respond properly by any API Extension will cause the whole request to fail with a
502
or504
. - Otherwise, if any API Extension finds that the result is not valid, the result will not be persisted and an error will be returned to the original caller of the API.
- Otherwise, if any API Extension returns updates, the result will be modified before returned to the original caller of the API.
API Extensions should operate on separate concerns. For example for a cart, it is fine to have an extension each for validating that a customer is allowed to purchase age-restricted products, calculating shipping costs and adding mandatory insurance. A counter-example is two API Extensions changing the price of a line item: One for adding extra costs for optional gift wrapping, the other reducing the price if an externally defined discount applies - If both extensions want to change the same line item, one will overwrite the result of the other. For this use case, the whole price calculation for line items should be performed inside a single extension.
You also need to find a balance between clean separation and the increasing latency risk when calling many parallel extensions.
Input
An HTTP
extension will be called via HTTP POST request. A AWSLambda
extension will be invoked, and the input is provided as the payload.
The following JSON body will be sent:
action
- String - Currently, eitherCreate
orUpdate
resource
- Reference to an object
An expanded reference to the resource that triggered this extension.
The object will be persisted as-is if no extension returns errors or updates. It is therefore identical to the result the user may see, with the exception of timestamps: The lastModifiedAt
field, and if it is a creation the createdAt
field, do not contain the final values. All other fields will be persisted if they are not overridden by an update of an extension.
Headers
For an HTTP
extension, some headers will be set:
Content-Type
-application/json
X-Correlation-ID
- A correlation ID can be used to track a request. The same correlation ID will be returned to the original caller of the API.
In addition, the Authorization
or the x-functions-key
header are set if configured.
Response
An extension with an HTTP
destination must set a proper HTTP status code (200
or 201
for successful responses, 400
for validation failures). All other status codes will be treated as a failure to respond properly.
An extension with an AWSLambda destination must return without errors (both for successful responses, and validation failures). Throwing an exception in Java, Python, C# or Go, or invoking the callback with an error in NodeJS, will be treated as a failure to respond properly.
Validation successful / No updates requested
The body should be completely empty or an empty list of requested updates.
An HTTP
extension must set a 200
or 201
HTTP status code.
Validation failed
errors
- Array of Error - At least one error needs to be present.
An HTTP
extension must set a 400
HTTP status code.
A AWSLambda
extension must add the following field to the JSON:
responseType
- String -"FailedValidation"
Error
code
- String
Needs to be one of the defined error codes, for exampleInvalidInput
orInvalidOperation
.message
- String
A human-readable description of the error.localizedMessage
- LocalizedString - Optional
A human-readable, localized description of the error. If a localized message is available for the user's locale settings, the Merchant Center displays it.extensionExtraInfo
- JSON Object - Optional
Any other information that should be returned to the API caller.
See Errors for more information.
Updates requested
actions
- Array of update actions for the resourceType.
Up to 100 update actions are allowed.
You can use all update actions for the particular resource:
- for
cart
you can use any cart update action. - for
order
you can use any order update action. - for
payment
you can use any payment update action. - for
customer
you can use any customer update action.
An HTTP
extension must set a 200
or 201
HTTP status code.
A AWSLambda
extension must add the following field to the JSON:
responseType
- String -"UpdateRequest"
Limits and error cases
An API Extension affects the performance and uptime of the API it is extending. Therefore, you should carefully consider the technology used to implement the API Extension, and your hosting options.
We believe that deploying an extension on a Function-as-a-Service is a good fit. You should get a very high up-time and auto-scaling at low costs. However, you should optimize your functions for good cold-start performance.
Independently of the technology choice you should make sure that the network latency between the commercetools platform region your project is running on and your extension is as low as possible.
Time limits
If an API Extension is not responding fast, the whole API call is blocked. We therefore enforce the following limits per default:
- An API Extension must return a result within 2 seconds to the commercetools platform. This includes the network latency between the two.
- For an API Extension with a
payment
trigger, the time limit can be raised to 10 seconds. - The commercetools platform must successfully establish a connection to the API Extension within 1 second.
The default timeout can be lowered.
We recommend that your API Extension returns results much more faster, though. A good target is to respond within less than 50 ms.
Error cases
In any error case (no response within the time limit or a bad response like a 500
HTTP status code) the API call fails. The API Extension is not retried within an API call. Further API calls will try to reach the API Extension again.
If the API Extension did not respond within the time limit, or no connection could be established, a 504
Gateway Timeout
HTTP status code and the error code ExtensionNoResponse
is returned to the API caller.
If the API Extension did respond, but the response couldn't be parsed successfully (for example a 500
HTTP status code, or an invalid JSON object), a 502
Bad Gateway
HTTP status code and the error code ExtensionBadResponse
is returned to the API caller. It includes details on why the response could not be accepted.
If the API Extension did respond with a list of updates, but those updates can not be applied to the resource (for example because a referenced resource does not exist), a 502
Bad Gateway
HTTP status code and the error code ExtensionUpdateActionsFailed
is returned to the API caller. It includes details on why the update action could not be applied, similar to what is returned when a 400
Bad Request
response is returned for the same update action.