BETA

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 - String
  • key - String - Optional - User-specific unique identifier for the extension
  • version - Number
  • createdAt - DateTime
  • createdBy - CreatedBy BETA
    Present on resources created after 2019-02-01 except for events not tracked.
  • lastModifiedAt - DateTime
  • lastModifiedBy - LastModifiedBy BETA
    Present on resources updated after 2019-02-01 except for events not tracked.
  • destination - Destination - Details where the extension can be reached
  • triggers - Array of Trigger - Describes what triggers the extension
  • 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) for payment extensions and 2000 ms (2 seconds) for all other extensions.

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
  • 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) for payment 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

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 format arn:aws:lambda:<region>:<accountid>:function:<functionName>.
  • accessKey - String
  • accessSecret - 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

Authorization Header

  • type - String - "AuthorizationHeader"
  • headerValue - String - Partially hidden on retrieval

The Authorization header will be set to the content of headerValue. The authentication scheme (for example Basic or Bearer) should be included in the headerValue.

As an example for Basic Authentication, the headerValue should be set to Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==.

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, and customer are 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
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
    If key is absent or null, 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) for payment 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 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. 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, 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.

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 example InvalidInput or InvalidOperation.
  • 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:

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.