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:
Build the
CanonicalizedOpenSearchHeadersstring from allX-Opensearch-*request headers.Build the
CanonicalizedResourcestring from the request path and query parameters.Assemble the string-to-sign from the HTTP method, headers, and canonicalized components.
Compute the HMAC-SHA1 signature over the string-to-sign using the AccessKey secret.
Base64-encode the HMAC digest to get the final signature value.
Set the
Authorizationheader using the AccessKey ID and signature.
Supported protocols and request methods
OpenSearch API V3 uses HTTP only.
Send
GETrequests to search for data.Send
POSTrequests 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:
Assign values to all
X-Opensearch-*headers. Omit any header whose value is empty.Sort the remaining headers alphabetically by name.
Convert each header name to lowercase (for example,
X-Opensearch-Noncebecomesx-opensearch-nonce).Remove all spaces around the
:delimiter in each header-value pair (for example,x-opensearch-nonce : valuebecomesx-opensearch-nonce:value).Join all header-value pairs with
\nand append\nafter the last pair.
When addingX-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 + ? + queryPush requests:
pathonly
Build the path
Encode the raw path string, replace %2F with /, and replace app_schema_demo with your application name:
| Request type | Path 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)
Assign values to each query parameter. Omit parameters with empty values.
Sort parameters alphabetically, first by key and then by value.
URL-encode each key and value per RFC 3986. Join each pair with
=.Concatenate all pairs with
&.Concatenate query clauses within the
queryparameter 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%3AfulljsonStep 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 afterOPENSEARCHin theAuthorizationheader value.
Parameters
| Parameter | Required | Description |
|---|---|---|
AccessKeyId | Yes | Your AccessKey ID. Included in the Authorization header. |
AccessKeySecret | Yes | Your AccessKey secret. Used to compute the HMAC. Never included in the request. |
VERB | Yes | HTTP method: GET, POST, PUT, HEAD, or DELETE. |
Content-MD5 | Conditional | The MD5 value of the HTTP request body (example: 4991ef0788236a8f280fed0db928e74e). Required for push requests. Leave blank for search requests. See RFC 2616. |
Content-Type | — | Media type of the request body (example: application/json). |
Date | Yes | Request 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. |
CanonicalizedOpenSearchHeaders | Conditional | All X-Opensearch-* headers, lowercased and sorted. Omit this line from the string-to-sign if the request has no X-Opensearch-* headers. |
CanonicalizedResource | Yes | The 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 parameter | Required | Request header | Required |
|---|---|---|---|
AccessKeySecret | Yes | Date | Yes |
VERB | Yes | X-Opensearch-Nonce | Yes |
Date | Yes | Authorization | Yes |
x-opensearch-nonce | Yes | ||
canonicalized_resource | Yes |
Push requests
| Signature parameter | Required | Request header | Required |
|---|---|---|---|
AccessKeySecret | Yes | Content-MD5 | Yes |
VERB | Yes | Date | Yes |
Content-MD5 | Yes | Authorization | Yes |
Date | Yes | ||
canonicalized_resource | Yes |
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:
| Component | Value |
|---|---|
| Request method | GET |
| AccessKey secret | yourAccessKeySecret |
| Content-MD5 | (blank — search request has no body) |
| Content-Type | application/json |
| Date | 2019-02-25T10:09:57Z |
| CanonicalizedOpenSearchHeaders | x-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%3AfulljsonSignature 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:57ZRequest string format
A complete request URL has the format host + CanonicalizedResource. In a request string, host specifies the endpoint of OpenSearch API.
| Request type | Example URL |
|---|---|
| Search data | http://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 suggestion | http://host/v3/openapi/apps/{appName}/suggest/{suggestName}/search?hits=10&query=%E6%A0%87%E9%A2%98 |
| Search by application ID | http://host/v3/openapi/apps/120001234 |
| Push data | http://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:
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.
Confirm
Content-MD5is blank forGETrequests.Confirm
CanonicalizedOpenSearchHeadersheader names are lowercased and sorted alphabetically.Confirm the
queryparameter clauses are concatenated with&&before URL encoding.Confirm the signature string and AccessKey secret are both UTF-8 encoded before computing the HMAC.