BETA

API Extensions

Extend the behavior of an API with your business logic.

The commercetools Composable Commerce APIs provide default data structures and default behavior that is useful for many 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 the 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.

You may host your API Extensions as you wish, but we provide integrations with serverless functions.

An API Extension affects the performance of the API it is extending. If it fails or takes a second longer to return a result, the whole API call fails or takes a second longer. Use Subscriptions instead of API Extensions when you can react to events asynchronously.

An API Extension is applied to API calls from all clients, including those provided by commercetools, like the Merchant Center. API Extensions are available for carts, orders, payments, and customers.

A Project can have a maximum of 25 API Extensions. When you create, update, or delete API Extensions, it may take upto a minute for the changes to update. For more information, see Eventual Consistency.

This document explains the creation of an API Extension, its input, responses, conditional triggers and limits and error cases. You can also see the step-by-step tutorial on how to set up an API Extension.

Representations

Extension

id
String

Unique identifier of the Extension.

version
Int

Current version of the Extension.

key
String

User-defined unique identifier of the Extension.

MinLength: 2MaxLength: 256Pattern: ^[A-Za-z0-9_-]+$
createdAt

Date and time (UTC) the Extension was initially created.

createdByBETA

Present on resources created after 1 February 2019 except for events not tracked.

lastModifiedAt

Date and time (UTC) the Extension was last updated.

lastModifiedByBETA

Present on resources created after 1 February 2019 except for events not tracked.

destination

The configuration for the Extension, including its type, location and authentication details.

triggers
Array of ExtensionTrigger

Describes what triggers the Extension.

timeoutInMs
Int

Maximum time (in milliseconds) that the Extension can respond within. If no timeout is provided, the default value 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.

Default: 2000

ExtensionDraft

key
String

User-defined unique identifier for the Extension.

MinLength: 2MaxLength: 256Pattern: ^[A-Za-z0-9_-]+$
destination

Defines where the Extension can be reached.

triggers
Array of ExtensionTrigger

Describes what triggers the Extension.

timeoutInMs
Int

Maximum time (in milliseconds) the Extension can respond within. If no timeout is provided, the default value 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 our support via the Support Portal and provide the Region, Project key, and use case.

Default: 2000

ExtensionPagedQueryResponse

PagedQueryResult with results containing an array of Extension.

limit
Int

Number of results requested.

offset
Int

Number of elements skipped.

count
Int

Actual number of results returned.

total
Int

Total number of results matching the query. This number is an estimation that is not strongly consistent. This field is returned by default. For improved performance, calculating this field can be deactivated by using the query parameter withTotal=false. When the results are filtered with a Query Predicate, total is subject to a limit.

results
Array of Extension

Extensions matching the query.

ExtensionResourceTypeId

Extensions are available for:

cart

Extension triggered for operations on Carts.

order

Extension triggered for operations on Orders.

payment

Extension triggered for operations on Payments.

customer

Extension triggered for operations on Customers.

ExtensionAction

An Extension gets called during any of the following requests of an API call, but before the result is persisted.

Create

An Extension gets called during a Create request.

Update

An Extension gets called during an Update request.

ExtensionDestination

A destination contains the configuration for the Extension, including its type, location and authentication details.

We believe deploying an API 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.

HTTPDestination

We recommend an encrypted HTTPS connection for production setups. However, we also accept unencrypted HTTP connections for development purposes. HTTP redirects will not be followed and cache headers will be ignored.

type
String
"HTTP"
url
String

URL to the target destination.

authentication

Authentication methods (such as Basic or Bearer).

AWSLambdaDestination

AWS Lambda limits the size of the payload to 6 MB. The limit also applies if the Lambda function is invoked by the API Gateway.
Do not use AWS Lambda if you anticipate that your API Extension will receive a JSON input exceeding 6 MB.

We recommend creating an Identify and Access Management (IAM) user with an accessKey and accessSecret pair, specifically for each Extension that only has the lambda:InvokeFunction permission on this function.

type
String
"AWSLambda"
arn
String

Amazon Resource Name (ARN) of the Lambda function in the format arn:aws:lambda:<region>:<accountid>:function:<functionName>.

accessKey
String

Partially hidden on retrieval for security reasons.

accessSecret
String

Partially hidden on retrieval for security reasons.

HTTPDestinationAuthentication

You can secure your HTTP destinations by setting an Authorization header using AuthorizationHeaderAuthentication. For calling Azure functions specifically, use AzureFunctionsAuthentication to specify the function key.

AuthorizationHeaderAuthentication

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

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

type
String
"AuthorizationHeader"
headerValue
String

Partially hidden on retrieval for security reasons.

AzureFunctionsAuthentication

To protect your Azure Function, set its authLevel to function and provide the function's key to be used inside the x-functions-key header. For more information, see the Azure Functions documentation.

To protect the secret key from being exposed, remove the code parameter and secret key from the URL. For example, use https://foo.azurewebsites.net/api/bar instead of https://foo.azurewebsites.net/api/bar?code=secret.

key
String

Partially hidden on retrieval for security reasons.

type
String
"AzureFunctions"

ExtensionTrigger

resourceTypeId

cart, order, payment, and customer are supported.

actions
Array of ExtensionAction

Create and Update requests are supported.

condition
String

Valid predicate that controls the conditions under which the API Extension is called. The Extension is not triggered when the specified condition is not fulfilled.

Get Extension

Get Extension by ID

GET
https://api.{region}.commercetools.com/{projectKey}/extensions/{id}
OAuth 2.0 Scopes:
manage_extensions:{projectKey}
Path parameters:
region
String

Region in which the Project is hosted.

projectKey
String

key of the Project.

id
String

id of the Extension.

Query parameters:
expand
The parameter can be passed multiple times.
Response:

200Extension

Request Example:cURL
curl -X GET https://api.{region}.commercetools.com/{projectKey}/extensions/{id} -i \
--header 'Authorization: Bearer ${BEARER_TOKEN}'

Get Extension by Key

GET
https://api.{region}.commercetools.com/{projectKey}/extensions/key={key}
OAuth 2.0 Scopes:
manage_extensions:{projectKey}
Path parameters:
region
String

Region in which the Project is hosted.

projectKey
String

key of the Project.

key
String

key of the Extension.

Query parameters:
expand
The parameter can be passed multiple times.
Response:

200Extension

Request Example:cURL
curl -X GET https://api.{region}.commercetools.com/{projectKey}/extensions/key={key} -i \
--header 'Authorization: Bearer ${BEARER_TOKEN}'

Query Extensions

GET
https://api.{region}.commercetools.com/{projectKey}/extensions
OAuth 2.0 Scopes:
manage_extensions:{projectKey}
Path parameters:
region
String

Region in which the Project is hosted.

projectKey
String

key of the Project.

Query parameters:
where
The parameter can be passed multiple times.
/^var[.][a-zA-Z0-9]+$/
Any string parameter matching this regular expression

Predicate parameter values.

The parameter can be passed multiple times.
sort
The parameter can be passed multiple times.
expand
The parameter can be passed multiple times.
limit
Int
offset
Int

Number of elements skipped.

withTotal
Boolean

Controls the calculation of the total number of query results. Set to false to improve query performance when the total is not needed.

Response:

200ExtensionPagedQueryResponse

Request Example:cURL
curl -X GET https://api.{region}.commercetools.com/{projectKey}/extensions -i \
--header 'Authorization: Bearer ${BEARER_TOKEN}'

Create Extension

POST
https://api.{region}.commercetools.com/{projectKey}/extensions
OAuth 2.0 Scopes:
manage_extensions:{projectKey}
Path parameters:
region
String

Region in which the Project is hosted.

projectKey
String

key of the Project.

Query parameters:
expand
The parameter can be passed multiple times.
Request Body:ExtensionDraft
Response:

201Extension

Request Example:cURL
curl -X POST https://api.{region}.commercetools.com/{projectKey}/extensions -i \
--header 'Authorization: Bearer ${BEARER_TOKEN}' \
--header 'Content-Type: application/json' \
--data-binary @- << DATA
{
"destination" : {
"type" : "HTTP",
"url" : "https://example.azurewebsites.net/api/extension",
"authentication" : {
"type" : "AzureFunctions",
"key" : "some-azure-function-code"
}
},
"triggers" : [ {
"resourceTypeId" : "cart",
"actions" : [ "Create", "Update" ]
} ],
"key" : "my-extension"
}
DATA

Update Extension

Update Extension by ID

POST
https://api.{region}.commercetools.com/{projectKey}/extensions/{id}
OAuth 2.0 Scopes:
manage_extensions:{projectKey}
Path parameters:
region
String

Region in which the Project is hosted.

projectKey
String

key of the Project.

id
String

id of the Extension.

Query parameters:
expand
The parameter can be passed multiple times.
Request Body:
version
Int

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

Update actions to be performed on the Extension.

Response:

200Extension

Request Example:cURL
curl -X POST https://api.{region}.commercetools.com/{projectKey}/extensions/{id} -i \
--header 'Authorization: Bearer ${BEARER_TOKEN}' \
--header 'Content-Type: application/json' \
--data-binary @- << DATA
{
"version" : 1,
"actions" : [ {
"action" : "setKey",
"key" : "my-new-extension-key"
} ]
}
DATA

Update Extension by Key

POST
https://api.{region}.commercetools.com/{projectKey}/extensions/key={key}
OAuth 2.0 Scopes:
manage_extensions:{projectKey}
Path parameters:
region
String

Region in which the Project is hosted.

projectKey
String

key of the Project.

key
String

key of the Extension.

Query parameters:
expand
The parameter can be passed multiple times.
Request Body:
version
Int

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

Update actions to be performed on the Extension.

Response:

200Extension

Request Example:cURL
curl -X POST https://api.{region}.commercetools.com/{projectKey}/extensions/key={key} -i \
--header 'Authorization: Bearer ${BEARER_TOKEN}' \
--header 'Content-Type: application/json' \
--data-binary @- << DATA
{
"version" : 1,
"actions" : [ {
"action" : "setKey",
"key" : "my-new-extension-key"
} ]
}
DATA

Update actions

Set Key

key
String

Value to set. If empty, any existing value will be removed.

MinLength: 2MaxLength: 256Pattern: ^[A-Za-z0-9_-]+$
action
String
"setKey"
Example: json
{
"action" : "setKey",
"key" : "keyString"
}

Change Triggers

action
String
"changeTriggers"
triggers
Array of ExtensionTrigger

New value to set. Must not be empty.

Example: json
{
"action" : "changeTriggers",
"triggers" : [ {
"resourceTypeId" : "cart",
"actions" : [ "Create", "Update" ],
"condition" : "field is defined and field has changed"
} ]
}

Change Destination

action
String
"changeDestination"
destination

New value to set. Must not be empty.

Example: json
{
"action" : "changeDestination",
"destination" : {
"type" : "HTTP",
"url" : "URL-String",
"authentication" : {
"type" : "AuthorizationHeader",
"headerValue" : "valueString"
}
}
}

Set TimeoutInMs

action
String
"setTimeoutInMs"
timeoutInMs
Int

Value to set. If not defined, the maximum value is used. If no timeout is provided, the default value 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 our support via the Support Portal and provide the Region, Project key, and use case.

Default: 2000

Delete Extension

Delete Extension by ID

DELETE
https://api.{region}.commercetools.com/{projectKey}/extensions/{id}
OAuth 2.0 Scopes:
manage_extensions:{projectKey}
Path parameters:
region
String

Region in which the Project is hosted.

projectKey
String

key of the Project.

id
String

id of the Extension.

Query parameters:
expand
The parameter can be passed multiple times.
version
Int

Last seen version of the resource.

Response:

200Extension

Request Example:cURL
curl -X DELETE https://api.{region}.commercetools.com/{projectKey}/extensions/{id}?version={version} -i \
--header 'Authorization: Bearer ${BEARER_TOKEN}'

Delete Extension by Key

DELETE
https://api.{region}.commercetools.com/{projectKey}/extensions/key={key}
OAuth 2.0 Scopes:
manage_extensions:{projectKey}
Path parameters:
region
String

Region in which the Project is hosted.

projectKey
String

key of the Project.

key
String

key of the Extension.

Query parameters:
expand
The parameter can be passed multiple times.
version
Int

Last seen version of the resource.

Response:

200Extension

Request Example:cURL
curl -X DELETE https://api.{region}.commercetools.com/{projectKey}/extensions/key={key}?version={version} -i \
--header 'Authorization: Bearer ${BEARER_TOKEN}'

API Extension within the flow of the API call

The API Extensions are called during the execution of a create or update request. The system 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 validation checks 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, that can run their business logic. API Extension can respond on of three ways:

  • If the Extension finds the result to be valid, and does not want to perform any changes, it can return a success. The system 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 system 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 update actions (for example if it wants to add mandatory insurance to the Cart, it can return an AddLineItem update). The system will validate that the update actions are valid for the given resource type and 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 returns 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 Composable Commerce API. 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 or 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 it is 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. An AWSLambda Extension will be invoked, and the input is provided as the payload.

action

Create or Update request.

resource

Expanded reference to the resource that triggered the 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 x-functions-key header is 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.

The response can optionally contain a list of update actions to be applied to the resource. The possible update actions are limited to those available for the resource that triggered the API Extension.

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.

An 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
    User-defined description of the error.
  • localizedMessage - LocalizedString - Optional
    Localized user-defined 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.

For more information, see Errors from an API Extension.

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.

An AWSLambda Extension must add the following field to the JSON:

  • responseType - String - "UpdateRequest"

Conditional triggers

By default, API Extensions are always called during an update or create action on the resource they are configured for. However, in certain use cases, the Extension should only be triggered when certain conditions are met. Specifying conditional triggers can eliminate unnecessary API calls to your Extension. This eases the load on your service as well as improves overall performance.

You can define conditional statements using the predicate syntax in the condition field of the ExtensionTrigger. The condition is evaluated during every create or update action to the configured resource, resulting in one of three outcomes:

  • The condition evaluates to true and the API Extension is called.
  • The condition evaluates to false, the API Extension is not called.
  • The predicate syntax can not be evaluated and the entire API call fails.

If your predicate contains optional fields, use the is defined operator to ensure that the field exists and has a non-null value. This will ensure that a missing field will not cause the evaluation of the condition to fail.

For further examples and use-cases, follow our tutorial on API Extensions.

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 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. This includes the network latency.
  • For an API Extension with a payment trigger, the time limit can be raised to 10 seconds.
  • The commercetools Composable Commerce API 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 faster, though. A good target is to respond within 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.

If the API Extension fails to evaluate a conditional trigger predicate, for example when a field in the predicate is not defined on the resource, the call to the API extension will fail. A 400 Bad Request HTTP status code and the error code ExtensionPredicateEvaluationFailed is returned to the API caller.