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 our 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 (e.g. 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, 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 - String
  • version - Number
  • key - String - Optional - User-specific unique identifier for the extension
  • destination - Destination - Details where the extension can be reached
  • triggers - Array of Trigger - Describes what triggers the extension
  • createdAt - DateTime
  • lastModifiedAt - DateTime

ExtensionDraft

  • key - String - Optional - User-specific unique identifier for the extension
  • destination - Destination - Details where the extension can be reached
  • triggers - Array of Trigger - Describes what triggers the extension

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. Native AWS Lambda will be added in the future, in the meantime the AWS API Gateway has to be used.

HTTP Destination

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.

HTTP Destination Authentication

Azure Functions Authentication

  • type - String - "AzureFunctions"
  • key - String

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, e.g. do not use:
https://foo.azurewebsites.net/api/bar?code=secret
url, but only:
https://foo.azurewebsites.net/api/bar.

Trigger

  • resourceTypeId - String - Currently, only cart is supported.
  • actions - Array of String - Currently, Create and Update 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
OAuth2 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
OAuth2 Scopes: manage_extensions:{projectKey}
Response Representation: Extension

Query Extensions

Endpoint: /{projectKey}/extensions
Method: GET
OAuth2 Scopes: manage_extensions:{projectKey}
Response Representation: PagedQueryResult with the results array of Extension
Query Parameters:

Create an Extension

Endpoint: /{projectKey}/extensions
Method: POST
OAuth2 Scopes: manage_extensions:{projectKey}
Request Representation: ExtensionDraft
Response Representation: Extension

Currently, a maximum of 25 extensions can be created per project.

Update Extension

Update Extension by ID

Endpoint: /{projectKey}/extensions/{id}
Method: POST
OAuth2 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
OAuth2 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
Please find below the individual update actions provided on this endpoint.


Set Key

  • action - String - "setKey"
  • key - String - Optional
    If key is absent or null, this field will be removed if it exists.

Change Triggers

  • action - String - "changeTriggers"
  • messages - Array of Trigger

Change Destination

  • action - String - "changeDestination"
  • destination - Destination

Delete Extension

Delete Extension by ID

Endpoint: /{projectKey}/extensions/{id}
Method: DELETE
OAuth2 Scopes: manage_extensions:{projectKey}
Query Parameters:

  • version - Number - Required

Delete Extension by Key

Endpoint: /{projectKey}/extensions/key={key}
Method: DELETE
OAuth2 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 (e.g. the cart) is not persisted yet. It is forwarded to the API Extensions, who can run their business logic. There are three ways for an API Extension to properly respond:

  • If the Extension finds the result to be valid, and does not want to perform any changes, it can simply return a success. The commercetools platform will persist the result.
  • If the Extension finds that the result is not valid (e.g. 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 (e.g. 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 (e.g. 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 SLA, a failure caused by an API Extension does not count as a failure of the commercetools platform. E.g. 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 (e.g. 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 or 504.
  • 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. E.g. 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.

The following JSON body will be sent:

  • action - String - Currently, either Create or Update
  • 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.

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. Every extension must return one of the following bodies:

Validation Successful / No Updates requested

The body should be completely empty or an empty list of requested updates.

Validation Failed

  • errors - Array of Error - At least one error needs to be present.

Error

  • code - String
    Needs to be one of the defined error codes, e.g. InvalidInput or InvalidOperation.
  • message - String
    A human-readable description of the error.
  • localizedMessage - LocalizedString - Optional
    A human-readable, localized description of the error. If available, the Merchant Center will use the correct localization to display the error.
  • extensionExtraInfo - JSON Object - Optional
    Any other information that should be returned to the API caller.

Updates requested

  • actions - Array of update actions for the resourceType.
    Up to ** update actions are allowed.

You can use all update actions for the particular resource. For example, for cart you can use any cart update action.

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 quickly, the whole API call is blocked. We therefore enforce the following limits:

  • An API Extension must return a result within 2 seconds to the commercetools platform. This includes the network latency between the two.
  • The commercetools platform must successfully establish a connection to the API Extension within 1 second.

We recommend that your API Extension returns results much more quickly, though. A good target is to respond within less than 50ms.

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 (e.g. 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 (e.g. 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.