All Products
Search
Document Center

OpenSearch:Signature method of OpenSearch API V3

Last Updated:Apr 01, 2026

OpenSearch authenticates every API request using symmetric encryption based on AccessKey pairs. Each pair consists of an AccessKey ID and an AccessKey secret — the ID identifies the caller, and the secret signs the request. Keep your AccessKey secret strictly confidential.

OpenSearch provides SDKs for Java, PHP, Python, and C#. The PHP, Python, and C# SDKs have built-in signature implementations. If an SDK is available for your language, use it and skip this topic. If you need to implement signing manually or debug a signature error, follow the steps below.

OpenSearch does not maintain the SDKs that are implemented based on this topic and the source code of official SDKs. Users should maintain these SDKs by themselves.

Prerequisites

Before you begin, ensure that you have:

  • An AccessKey ID and AccessKey secret obtained from the Alibaba Cloud console

  • An OpenSearch application (Advanced or Standard)

How it works

Signing a request involves six steps:

  1. Build the CanonicalizedOpenSearchHeaders string from all X-Opensearch-* request headers.

  2. Build the CanonicalizedResource string from the request path and query parameters.

  3. Assemble the string-to-sign from the HTTP method, headers, and canonicalized components.

  4. Compute the HMAC-SHA1 signature over the string-to-sign using the AccessKey secret.

  5. Base64-encode the HMAC digest to get the final signature value.

  6. Set the Authorization header using the AccessKey ID and signature.

Supported protocols and request methods

OpenSearch API V3 uses HTTP only.

  • Send GET requests to search for data.

  • Send POST requests to push data.

Step 1: Build CanonicalizedOpenSearchHeaders

CanonicalizedOpenSearchHeaders contains all request headers prefixed with X-Opensearch- (for example, X-Opensearch-Nonce). If the request has no X-Opensearch-* headers, omit this component entirely from the string-to-sign.

To build the string:

  1. Assign values to all X-Opensearch-* headers. Omit any header whose value is empty.

  2. Sort the remaining headers alphabetically by name.

  3. Convert each header name to lowercase (for example, X-Opensearch-Nonce becomes x-opensearch-nonce).

  4. Remove all spaces around the : delimiter in each header-value pair (for example, x-opensearch-nonce : value becomes x-opensearch-nonce:value).

  5. Join all header-value pairs with \n and append \n after the last pair.

When adding X-Opensearch-* headers to the actual HTTP request, use the original mixed-case format (X-Opensearch-Nonce), not the lowercase form used for signing.

X-Opensearch-Nonce format: A 10-digit timestamp concatenated with a 6-digit random value in the range 100000–999999 (for example, 155108**********).

Step 2: Build CanonicalizedResource

The format depends on the request type:

  • Search requests: path + ? + query

  • Push requests: path only

Build the path

Encode the raw path string, replace %2F with /, and replace app_schema_demo with your application name:

Request typePath format
Search data/v3/openapi/apps/<app-name>/search
Search by suggestion/v3/openapi/suggestions/<suggestion-name>/actions/search
Search by application ID/v3/openapi/apps/<app-id>
Push data/v3/openapi/apps/<app-name>/<table-name>/actions/bulk

Build the query string (search requests only)

  1. Assign values to each query parameter. Omit parameters with empty values.

  2. Sort parameters alphabetically, first by key and then by value.

  3. URL-encode each key and value per RFC 3986. Join each pair with =.

  4. Concatenate all pairs with &.

  5. Concatenate query clauses within the query parameter using && before encoding.

Example CanonicalizedResource for a search request:

/v3/openapi/apps/app_schema_demo/search?fetch_fields=name&query=query%3Dname%3A%27%E6%96%87%E6%A1%A3%27%26%26sort%3Did%26%26config%3Dformat%3Afulljson

Step 3: Assemble the string-to-sign

"Authorization: OPENSEARCH " + AccessKeyId + ":" + Signature

Signature = base64(hmac-sha1(AccessKeySecret,
            VERB + "\n"
            + Content-MD5 + "\n"
            + Content-Type + "\n"
            + Date + "\n"
            + CanonicalizedOpenSearchHeaders
            + CanonicalizedResource))

All parameters must appear in the order shown above. The string-to-sign must be UTF-8 encoded. If the string contains Chinese characters, encode them to UTF-8 before computing the HMAC.

Include a space after OPENSEARCH in the Authorization header value.

Parameters

ParameterRequiredDescription
AccessKeyIdYesYour AccessKey ID. Included in the Authorization header.
AccessKeySecretYesYour AccessKey secret. Used to compute the HMAC. Never included in the request.
VERBYesHTTP method: GET, POST, PUT, HEAD, or DELETE.
Content-MD5ConditionalThe MD5 value of the HTTP request body (example: 4991ef0788236a8f280fed0db928e74e). Required for push requests. Leave blank for search requests. See RFC 2616.
Content-TypeMedia type of the request body (example: application/json).
DateYesRequest timestamp in ISO 8601 format: YYYY-MM-DDThh:mm:ssZ (UTC). Example: 2019-02-25T10:09:57Z. OpenSearch rejects requests where the timestamp differs from the server time by more than 15 minutes, returning HTTP 403.
CanonicalizedOpenSearchHeadersConditionalAll X-Opensearch-* headers, lowercased and sorted. Omit this line from the string-to-sign if the request has no X-Opensearch-* headers.
CanonicalizedResourceYesThe canonicalized request path and query string.

Step 4: Compute the HMAC-SHA1 signature

Use HMAC-SHA1 as defined in RFC 2104 to sign the string-to-sign with the AccessKey secret as the key.

Step 5: Base64-encode the digest

Base64-encode the raw HMAC-SHA1 digest bytes to produce the final signature value.

Step 6: Set the Authorization header

Authorization: OPENSEARCH <AccessKeyId>:<Signature>

Python 3 example:

headers['Authorization'] = 'OPENSEARCH ' + 'LTAI****************' + ':' + '1P7tfE****'

Required headers by request type

Search requests

Signature parameterRequiredRequest headerRequired
AccessKeySecretYesDateYes
VERBYesX-Opensearch-NonceYes
DateYesAuthorizationYes
x-opensearch-nonceYes
canonicalized_resourceYes

Push requests

Signature parameterRequiredRequest headerRequired
AccessKeySecretYesContent-MD5Yes
VERBYesDateYes
Content-MD5YesAuthorizationYes
DateYes
canonicalized_resourceYes

The header values sent in the HTTP request must exactly match the values used during signature calculation. Include the Content-MD5, Content-Type, Date, CanonicalizedOpenSearchHeaders, and Authorization headers in every request to avoid errors.

Complete example

This example signs a search request with the following values:

ComponentValue
Request methodGET
AccessKey secretyourAccessKeySecret
Content-MD5(blank — search request has no body)
Content-Typeapplication/json
Date2019-02-25T10:09:57Z
CanonicalizedOpenSearchHeadersx-opensearch-nonce:1551089397451704
CanonicalizedResource/v3/openapi/apps/app_schema_demo/search?fetch_fields=name&query=query%3Dname%3A%27%E6%96%87%E6%A1%A3%27%26%26sort%3Did%26%26config%3Dformat%3Afulljson

String-to-sign (use this intermediate value to verify your implementation):

GET\n
\n
application/json\n
2019-02-25T10:09:57Z\n
x-opensearch-nonce:1551089397451704\n
/v3/openapi/apps/app_schema_demo/search?fetch_fields=name&query=query%3Dname%3A%27%E6%96%87%E6%A1%A3%27%26%26sort%3Did%26%26config%3Dformat%3Afulljson

Signature calculation (Python 3):

import hmac
import base64

signature_string = '\n'.join([
    'GET',
    '',
    'application/json',
    '2019-02-25T10:09:57Z',
    'x-opensearch-nonce:1551089397451704',
    '/v3/openapi/apps/app_schema_demo/search?fetch_fields=name&query=query%3Dname%3A%27%E6%96%87%E6%A1%A3%27%26%26sort%3Did%26%26config%3Dformat%3Afulljson'
])

signature_hmac = hmac.new(
    'yourAccessKeySecret'.encode('utf-8'),
    signature_string.encode('utf-8'),
    'sha1'
)
signature = base64.b64encode(signature_hmac.digest())

Final signature: 1P7tfE****

Request headers:

Content-MD5:
Content-Type: application/json
Authorization: OPENSEARCH LTAI****************:1P7tfE****
X-Opensearch-Nonce: 1551089397451704
Date: 2019-02-25T10:09:57Z

Request string format

A complete request URL has the format host + CanonicalizedResource. In a request string, host specifies the endpoint of OpenSearch API.

Request typeExample URL
Search datahttp://host/v3/openapi/apps/app_schema_demo/search?fetch_fields=name&query=query%3Dname%3A%27%E6%96%87%E6%A1%A3%27%26%26sort%3Did%26%26config%3Dformat%3Afulljson
Search by suggestionhttp://host/v3/openapi/apps/{appName}/suggest/{suggestName}/search?hits=10&query=%E6%A0%87%E9%A2%98
Search by application IDhttp://host/v3/openapi/apps/120001234
Push datahttp://host/v3/openapi/apps/app_schema_demo/tab/actions/bulk

Application schema template

{
  "name": "app_schema_demo",
  "type": "standard",
  "schema": {
    "indexes": {
      "search_fields": {
        "id": {
          "fields": ["id"]
        },
        "name": {
          "fields": ["name"],
          "analyzer": "chn_standard"
        },
        "phone": {
          "fields": ["phone"],
          "analyzer": "fuzzy"
        },
        "int_arr": {
          "fields": ["int_arr"]
        },
        "literal_arr": {
          "fields": ["literal_arr"]
        },
        "cate_id": {
          "fields": ["cate_id"]
        }
      },
      "filter_fields": ["id", "int_arr", "literal_arr", "float_arr", "cate_id"]
    },
    "tables": {
      "tab": {
        "name": "tab",
        "fields": {
          "id": { "name": "id", "type": "INT", "primary_key": true },
          "name": { "name": "name", "type": "TEXT", "primary_key": false },
          "phone": { "name": "phone", "type": "SHORT_TEXT", "primary_key": false },
          "int_arr": { "name": "int_arr", "type": "INT_ARRAY", "primary_key": false },
          "literal_arr": { "name": "literal_arr", "type": "LITERAL_ARRAY", "primary_key": false },
          "float_arr": { "name": "float_arr", "type": "FLOAT_ARRAY", "primary_key": false },
          "cate_id": { "name": "cate_id", "type": "INT", "primary_key": false }
        },
        "primary_table": true
      }
    },
    "route_field": null
  },
  "data_sources": [],
  "first_ranks": {},
  "second_ranks": {},
  "summary": [],
  "fetch_fields": ["id", "name", "phone", "int_arr", "literal_arr", "float_arr", "cate_id"],
  "quota": {
    "qps": 6,
    "doc_size": 0.3
  }
}

Troubleshooting

HTTP 403 error on every request

The most common cause is a timestamp outside the 15-minute tolerance window. Make sure the Date header uses current UTC time in ISO 8601 format (YYYY-MM-DDThh:mm:ssZ) and that your system clock is synchronized.

Signature mismatch

Check the following in order:

  1. Verify the string-to-sign matches the intermediate value in the example above — compute the string-to-sign for your request and log it before hashing.

  2. Confirm Content-MD5 is blank for GET requests.

  3. Confirm CanonicalizedOpenSearchHeaders header names are lowercased and sorted alphabetically.

  4. Confirm the query parameter clauses are concatenated with && before URL encoding.

  5. Confirm the signature string and AccessKey secret are both UTF-8 encoded before computing the HMAC.