# Custom APIs

B2C Commerce provides powerful tools to customize shopper experiences. Today SCAPI can be customized via [Hooks](/docs/commerce/b2c-commerce/guide/extensibility_via_hooks.md), where developers use the [B2C Commerce Script API](https://salesforcecommercecloud.github.io/b2c-dev-doc/) to add filters or modify responses before they get sent back to the calling application. A composable storefront calls custom APIs via hooks. Storefront Next calls them either as a generated typed client built on the [SCAPI client](/docs/commerce/b2c-commerce/guide/sfnext-api-integration.md#custom-apis) primitives, or directly with native `fetch`.

To further leverage existing development tools, B2C Commerce provides a framework enabling developers to write custom script code, such as controllers, and expose this functionality as a custom *REST API* under the SCAPI framework.

The [SCAPI Custom APIs video](https://salesforce.vidyard.com/watch/ydP6b8do74w53tYnRtaAEs) walks you through how to build a cartridge with a Custom API. If you do not have access to the code samples provided in the video, see [Salesforce Commerce Cloud GitHub Repositories and Access](https://developer.salesforce.com/docs/commerce/b2c-commerce/guide/b2c-github-repo-access.md).

::video{src="ydP6b8do74w53tYnRtaAEs" title="SCAPI Custom APIs" type="vidyard"}

## Overview

A Custom API URL has the following structural format:

`https://{shortCode}.api.commercecloud.salesforce.com/custom/{apiName}/{apiVersion}/organizations/{organizationId}/{endpointPath}`

The term `custom` must be used as API family. The parameters `apiName`, `apiVersion`, and `endpointPath` can be defined as described below.

In order to create a Custom API, three components are required:

- The **API contract** as an [OAS 3.0](https://spec.openapis.org/oas/v3.0.0) schema file.
- The **API implementation** as a script using the [B2C Commerce Script API](https://salesforcecommercecloud.github.io/b2c-dev-doc/).
- The **API mapping** defined in an `api.json` file.

In this guide, we will look at an example called *Loyalty Info API* that can be used to retrieve the loyalty status for a given customer. This graphic shows the required components and their relationship:

![Custom API integration with B2C Commerce platform](https://a.sfdcstatic.com/developer-website/sfdocs/commerce-cloud/media/commerce-api/custom-api-overview.svg)

:::tip
Only existing customers can access some of the links on this page. Visit [Salesforce Commerce Cloud GitHub Repositories and Access](https://developer.salesforce.com/docs/commerce/b2c-commerce/guide/b2c-github-repo-access.md) for information about how to get access to the repositories.
:::

The components will be further explained in the next sections. Note the following details from the diagram:

- A dashed line shows where this URL segment is defined.
- The blue parameter is the endpoint identifier. It is used to bind the endpoint to the implementation and appears in all three components.

### Cartridge

Custom APIs are defined within custom code cartridges. In the `cartridge` directory, a new folder `rest-apis` must be created which is the root folder for all APIs in that cartridge.

In the `rest-apis` folder, subdirectories represent the actual APIs. The names of these directories represent the API names as they are addressed by the URL. Each directory contains the files for the three components representing this API.

**Example:** The structure for the example API called `loyalty-info` is expected to look like this:

```sfdocs-code {"lang":"bash", "title": "Cartridge structure" }
/my-cartridge
    /cartridge
        package.json
        /rest-apis
            /loyalty-info
                api.json
                schema.yaml
                schema_v2.yaml
                script.js
                script_v2.js
        /pipelines
        /scripts
        /controllers
```

:::important
API directory names can only contain alphanumeric lowercase characters and hyphens.
:::

:::tip
Due to the [registration process](#registration-of-endpoints), it is recommended to start implementing Custom API endpoints in a new code version.
:::

### Contract

The API contract must be provided by an OAS 3.0 schema file in YAML format. For more information about OAS 3.0, refer to the [OAS 3.0 specification](https://spec.openapis.org/oas/v3.0.0).

In this schema file, the **API version** and the **endpoints** are defined. The API version is located in the `info.version` field and is transformed into a URL version by using the major version segment and prepending it with `v` (for example, `1.0.1` → `v1`).

The endpoints are defined as part of the `paths` object and can implement multiple operations (that is, HTTP methods). Each operation is required to have a property called `operationId`, which *must* be unique within the schema file. It is used for mapping the endpoint to the implementation. The `path` of the endpoint becomes the `endpointPath` in the URL structure.

To secure the endpoint, define a valid security scheme as a shared component and apply it at the endpoint or global level. See [Authentication](/docs/commerce/b2c-commerce/guide/custom-api-authentication.md) for more details.

**Example:** The schema for a `/customers?c_customer_id={id}` endpoint in version `v1` is expected to look like this:

```sfdocs-code {"lang":"yaml", "title": "schema.yaml:" }
openapi: 3.0.0
info:
  version: 1.0.1
  title: Loyalty Information API
components:
  securitySchemes:
    ShopperToken:
      type: oauth2
      flows:
        authorizationCode:
          authorizationUrl: https://my-shortcode.api.commercecloud.salesforce.com/shopper/auth/v1/organizations/my-org-id/oauth2/authorize
          tokenUrl: https://my-shortcode.api.commercecloud.salesforce.com/shopper/auth/v1/organizations/my-org-id/oauth2/token
          scopes:
            c_loyaltyInfo_r: Read loyalty information of a customer
        clientCredentials:
          tokenUrl: https://my-shortcode.api.commercecloud.salesforce.com/shopper/auth/v1/organizations/my-org-id/oauth2/token
          scopes:
            c_loyaltyInfo_r: Read loyalty information of a customer
paths:
  /customers:
    get:
      summary: Get loyalty information for customer
      operationId: getLoyaltyInfo
      parameters:
        - in: query
          name: c_customer_id
          required: true
          schema:
            type: string
        - in: query
          name: siteId
          required: true
          schema:
            type: string
            minLength: 1
        - in: query
          name: locale
          required: false
          schema:
            type: string
            minLength: 1
      responses:
        "200":
          description: Shows loyalty information for the customer
          content:
            application/json:
              schema:
                type: object
                properties:
                  tier:
                    type: string
                  points:
                    type: integer
        "404":
          description: Shows that the customer is not found
          content:
            application/json:
              examples:
                not_found:
                  value:
                    {
                      "type": "https://api.commercecloud.salesforce.com/documentation/error/v1/custom-errors/customer-not-found",
                    }
security:
  - ShopperToken: ["c_loyaltyInfo_r"]
```

### Implementation

The API implementation is provided by a script file using the B2C Commerce Script API. The function name *must* match the value of the endpoint’s `operationId` in the contract.

**Example:** A valid implementation for `/customers?c_customer_id={id}` based on the definition in the contract could look like this:

```sfdocs-code {"lang":"javascript", "title": "script.js:" }
var RESTResponseMgr = require("dw/system/RESTResponseMgr");

exports.getLoyaltyInfo = function () {

  var customerId = request
    .getHttpParameterMap()
    .get("c_customer_id")
    .getStringValue();

  /*
    ... some lookup of loyalty data based on customer id ...
  */

  if (customerId === "customer1") {

    var info = {
      tier: "silver",
      points: 14275
    };

    RESTResponseMgr
      .createSuccess(info)
      .render();

  } else {

    RESTResponseMgr
      .createError(404, "customer-not-found", "Customer Unknown", "You provided an unknown customer ID.")
      .render();

  }

};

exports.getLoyaltyInfo.public = true;
```

:::important
Always return responses in JSON format. Errors should additionally adhere to the SCAPI error format as described in [RFC 9457](https://datatracker.ietf.org/doc/html/rfc9457), with at least the `type` field being present. Response bodies are subject to validation in the future and may be declined if they don’t meet these requirements.
:::

:::important
Make sure that the implementation contains high quality, well-tested code with sufficient error handling. In order to protect the platform, Custom APIs use a Circuit Breaker mechanism that will block API requests when the implementation throws too many errors. See [Circuit Breaker](/docs/commerce/b2c-commerce/guide/custom-api-circuit-breaker.md) for more information.
:::

:::tip
To learn how to create JSON responses in your scripts with ease, have a look at the documentation of the Script API class [`RESTResponseMgr`](https://salesforcecommercecloud.github.io/b2c-dev-doc/docs/upcoming/scriptapi/html/api/class_dw_system_RESTResponseMgr.html).
:::

### Mapping

The `api.json` file provides the API mapping. It contains a list of endpoints, and for each entry the endpoint name, its schema file, and the name of the implementing script is defined.

**Example:** A valid mapping for `/customers?c_customer_id={id}` based on the definitions in the contract and implementation is expected to look like this:

```sfdocs-code {"lang":"json", "title": "api.json:" }
{
  "endpoints": [
    {"endpoint": "getLoyaltyInfo", "schema": "schema.yaml", "implementation": "script"},
    {"endpoint": "getLoyaltyInfo", "schema": "schema_v2.yaml", "implementation": "script_v2"}
  ]
}
```

:::important
Relative paths are not supported. The schema and implementation must be located at the same level as the corresponding `api.json`.
:::

:::important
Provide the implementation name without the file extension.
:::

## Registration of Endpoints

When the Custom API endpoints have been correctly defined as previously described, they must be registered in order to be accessible.

The registration is triggered by **activating the code version** containing the new API definitions. Whenever changes to an API are required, it is recommended to work in a new code version and to switch the version when ready.

## Versioning

Custom API versions are represented in two different formats, the **API version** and the **URL version**.

The API version is defined in the contract as part of the `info.version` field and applies to all endpoints defined in this file. The value must be numeric but can have multiple segments separated by a dot.

In the URL, the version is transformed by using the major version segment and prepending it with `v` . For example, this list shows valid API versions and their corresponding URL versions:

- 1 → v1
- 1.0 → v1
- 2.0 → v2
- 2.1.1 → v2

It is recommended to introduce breaking changes in a new major version. See the [SCAPI Change Policy](/docs/commerce/b2c-commerce/guide/scapi-get-started.md#change-policy) for details.

## System Query Parameters

The following system query parameters exist and can be used when defined in the contract:

- **`siteId`** - The site of the current request
- **`locale`** - The locale of the current request

Make sure you understand the meaning of these parameters and when to use them. For example, the system uses the `siteId` to identify the site context, so using this parameter only makes sense for a Shopper API request. For an Admin API request that requires a site, for example: an API that returns objects filtered by site, use a custom query parameter like `c_siteId`. Note that the site is a query parameter instead of a path parameter, which is different from other ways of customization, such as using controllers and hooks.
The parameter `locale` is used to set the locale of the request, and behaves the same way as the Script API method `dw.system.Request#setLocale`.

If a `siteId` is not provided, the request is treated as a SCAPI Admin API request and the Business Manager site is used. If a `locale` is not provided in a request, the site’s default locale is used.

:::note
The parameter `siteId` is also used to register a Custom API. This means that if you don't add the cartridge containing a Custom API to a site cartridge path, any request to it with that `siteId` results in a `404` response. For Admin APIs, the cartridge must be added to the cartridge path of the Business Manager site.
:::

Don't use a shopper token for an Admin API request. In these scenarios, use an `AmOAuth2` token.

**Example 1**: Define the system query parameter using the name for `siteId` and type `string`. If the parameter isn't a valid string, the request is declined.

```yaml
- in: query
  name: siteId
  required: true
  schema:
    type: string
    minLength: 1
```

**Example 2**: Define the system query parameter with the name `locale` and the type `string`. If the parameter isn't a valid string, the request is declined.

```yaml
- in: query
  name: locale
  required: false
  schema:
    type: string
    minLength: 1
```

**Example 3:** Make a Custom API request with system query parameters:

```http
GET https://my-shortcode.api.commercecloud.salesforce.com/custom/loyalty-info/v1/organizations/my-org-id/customers?c_customer_id=customer1&siteId=MySite&locale=en-US
```

:::important
Define system query parameters with the correct `schema` field as shown previously. Setting `required` to `false` means that the value is not required. When `required` is `true`, the request is refused if the parameter is missing or empty.
:::

## Shopper and Admin API Endpoints

Like the non-custom B2C Commerce APIs (SCAPI), each endpoint is either a Shopper or an Admin API endpoint. For general information on how to differentiate those API groups, see [SCAPI Groups](https://developer.salesforce.com/docs/commerce/commerce-api/guide/scapi-get-started.md#scapi-groups).

For Custom APIs, there are three main differences between Shopper and Admin endpoints:

1. Shopper endpoints have stricter limits for request body size and API quotas. See [Limitations](#limitations) for specific values.
2. Shopper endpoints require the `siteId` query parameter, while Admin endpoints must omit this parameter. Requests without a `siteId` are treated as Admin API requests.
3. Shopper endpoints require the `ShopperToken` security scheme, while Admin endpoints require the `AmOAuth2` security scheme, and each endpoint can have only one security scheme. See [Authentication](/docs/commerce/b2c-commerce/guide/custom-api-authentication.md) for details on how to use these.

## Supported HTTP Methods

Supported methods for Custom APIs include:

- GET
- POST
- PUT
- PATCH
- DELETE
- HEAD
- OPTIONS

The following example extends the Loyalty Information API with a POST method with which an admin can update the loyalty information:

```sfdocs-code {"lang":"yaml", "title": "schema.yaml:" }
openapi: 3.0.0
info:
  version: 1.0.1
  title: Loyalty Information API
components:
  securitySchemes:
    AmOAuth2:
      type: oauth2
      flows:
        authorizationCode:
          authorizationUrl: https://account.demandware.com/dwsso/oauth2/authorize
          tokenUrl: https://account.demandware.com/dwsso/oauth2/access_token
          scopes:
            c_loyaltyInfo_rw: Read or Update loyalty information of a customer
        clientCredentials:
          tokenUrl: https://account.demandware.com/dwsso/oauth2/access_token
          scopes:
            c_loyaltyInfo_rw: Read or Update loyalty information of a customer
paths:
  /customers/{customerId}:
    post:
      summary: Update loyalty information for customer
      operationId: updateLoyaltyInfo
      parameters:
        - in: path
          name: customerId
          required: true
          schema:
            type: string
      requestBody:
        content:
          'application/json':
            schema:
              type: object
              required:
                - points
              properties:
                points:
                  type: number
                  minimum: 1
                  maximum: 99999
      responses:
        "200":
          description: Shows loyalty information for the customer
          content:
            application/json:
              schema:
                type: object
                properties:
                  tier:
                    type: string
                  points:
                    type: integer
        "404":
          description: Shows that the customer is not found
          content:
            application/json:
              examples:
                not_found:
                  value:
                    {
                      "type": "https://api.commercecloud.salesforce.com/documentation/error/v1/custom-errors/customer-not-found",
                    }
security:
  - AmOAuth2: ["c_loyaltyInfo_rw"]
```

Here is a sample implementation for this endpoint:

```sfdocs-code {"lang":"javascript", "title": "script.js:" }
var RESTResponseMgr = require("dw/system/RESTResponseMgr");

exports.updateLoyaltyInfo = function () {

  var customerId = request
    .getSCAPIPathParameters()
    .get('customerId');

  var requestBody = request
    .httpParameterMap
    .requestBodyAsString;
  var requestJSON = JSON.parse( requestBody );

  /*
    ... Update loyalty points based on customer id and request body ...
  */


  if (customerId === "customer1") {

    var updatedInfo = {
      tier: "silver",
      points: requestJSON.points
    };

    RESTResponseMgr
      .createSuccess(updatedInfo)
      .render();

  } else {

    RESTResponseMgr
      .createError(404, "customer-not-found", "Customer Unknown", "You provided an unknown customer ID.")
      .render();

  }

};

exports.updateLoyaltyInfo.public = true;
```

:::tip
To learn more about new convenience methods for SCAPI path parsing, refer to the [`Request`](https://salesforcecommercecloud.github.io/b2c-dev-doc/docs/upcoming/scriptapi/html/index.html?target=class_dw_system_Request.html) class in Script API.
:::

:::note
The attribute `additionalProperties` is NOT allowed when defining the request body schema. To take advantage of request body validation and build robust APIs, define the request body schema in detail. If the request body schema of an endpoint contains `additionalProperties`, the endpoint is not registered.
:::

## Validation

All request parameters *must* be defined in the contract as requests with unknown parameters are declined. Custom query parameters must be prefixed with `c_`. [System query parameters](#system-query-parameters) must be added to the contract if used.

**Example:** Defining a mandatory query parameter with the name `c_status` and the type `integer`. If the parameter is not present or is not a valid integer, the request is declined.

```yaml
/error:
  get:
    summary: Throw an error
    operationId: throwError
    parameters:
      - in: query
        name: c_status
        required: true
        schema:
          type: integer
```

## Sample Custom API Collection

:::tip
To get access to this repository, complete the trail [Tools & Resources for Salesforce B2C Commerce Developers](https://trailhead.salesforce.com/content/learn/modules/b2c-developer-resources-and-tools/b2c-developer-access-repositories).
:::

Our community is maintaining a Custom API collection within the [Sample Hooks Collection](https://developer.salesforce.com/docs/commerce/commerce-api/guide/extensibility_via_hooks.md#sample-hooks-collection), providing the functionality earlier offered with hooks as new Custom APIs. Here are a few APIs available with this collection:

- URL Resolution
- URL List API
- Guest Order Look Up
- Customer Groups API
- Mini Product
- Send Mail API
- Geo IP API

For more information, check out the [Custom APIs Collection](https://github.com/SalesforceCommerceCloud/sfcc-hooks-collection/tree/main/commerce-cloud-code/plugin_hooktacular/cartridge/rest-apis) on the Salesforce Commerce Cloud GitHub repository.

## Limitations

The following limitations are in place:

- General:
  - Transactions can’t be committed in the case of GET requests.
  - The schema attribute `additionalProperties` is not allowed.
  - Shopper and Admin APIs must adhere to the applicable API quotas. For quota details, see [API Quotas documentation](https://salesforcecommercecloud.github.io/b2c-dev-doc/docs/current/quota/html/API_Quotas.html).
- Shopper APIs:
  - Default request runtime of 60 seconds, configurable up to 120 seconds via the [Timeouts API](https://developer.salesforce.com/docs/commerce/commerce-api/references/timeouts?meta=Summary).

    :::note
    A 10-second runtime is recommended for Shopper endpoints. Set the runtime via the Timeouts API.
    :::

  - Maximum request body size of 5 MiB (5,242,880 bytes).

  - `STOREFRONT` activity type. Storefront quota limits are applied.
- SCAPI Admin APIs:
  - Default request runtime of 60 seconds, configurable up to 120 seconds via the [Timeouts API](https://developer.salesforce.com/docs/commerce/commerce-api/references/timeouts?meta=Summary).
  - Maximum request body size of 20 MB (20,000,000 bytes).
  - `BUSINESS_MANAGER` activity type. Default quota limits are applied.
