OpenSearch authenticates every API request using HMAC-SHA1 signatures. Each request must include an Authorization header containing your AccessKey ID and a computed signature derived from your AccessKey secret.
The OpenSearch SDKs for PHP, Python, and C# handle signature calculation automatically. The Java SDK is also available but does not implement signature calculation. If you use a PHP, Python, or C# SDK, skip this topic. Read on only if you are implementing a custom HTTP client or working in an unsupported language.
How it works
Each signed request goes through four construction stages before it reaches the OpenSearch server:
AccessKeySecret
│
├─► Build CanonicalizedOpenSearchHeaders ─┐
│ │
├─► Build CanonicalizedResource ─┤
│ │
▼ ▼
string-to-sign ──► HMAC-SHA1 + Base64 ──► Signature
│
▼
Authorization headerThe Authorization header has this format:
Authorization: OPENSEARCH <AccessKeyId>:<Signature>A space is required after OPENSEARCH.
The signature is computed as:
Signature = base64(hmac-sha1(AccessKeySecret,
VERB + "\n"
+ Content-MD5 + "\n"
+ Content-Type + "\n"
+ Date + "\n"
+ CanonicalizedOpenSearchHeaders
+ CanonicalizedResource))Supported applications and protocols
Application types: Advanced applications, Standard applications
Protocol: HTTP only
Request methods: GET for search requests, POST for push-data requests
Prerequisites
Before you begin, make sure you have:
An AccessKey pair (AccessKey ID and AccessKey secret) issued through alibabacloud.com
An OpenSearch application (Advanced or Standard edition)
Keep your AccessKey secret strictly confidential. It is used to sign every request.
Step 1: Assemble the string-to-sign
The string-to-sign concatenates the following components in order:
| Component | Required | Notes |
|---|---|---|
VERB | Yes | HTTP method: GET, POST, PUT, HEAD, or DELETE |
Content-MD5 | Conditional | MD5 hash of the request body (e.g., 4991ef0788236a8f280fed0db928e74e). Leave blank for requests without a body, such as search requests. See RFC 2616. |
Content-Type | No | Example: application/json |
Date | Yes | UTC timestamp in ISO 8601 format: YYYY-MM-DDThh:mm:ssZ. Example: 2019-02-25T10:09:57Z. Requests are rejected with HTTP 403 if the timestamp differs from the server time by more than 15 minutes. |
CanonicalizedOpenSearchHeaders | Conditional | Required when the request includes X-Opensearch-* headers. See Step 2. |
CanonicalizedResource | Yes | The request path (plus query string for search requests). See Step 3. |
Each component is separated by a newline character (\n). Components with no value (such as Content-MD5 in a search request) contribute an empty string — the \n separator is still required.
Step 2: Build CanonicalizedOpenSearchHeaders
CanonicalizedOpenSearchHeaders covers all request headers whose names start with X-Opensearch-. The most common one is X-Opensearch-Nonce.
`X-Opensearch-Nonce` format: a 10-digit Unix timestamp followed by a 6-digit random number in the range 100000–999999. Example: 1551089397451704.
To build the string:
List all
X-Opensearch-*headers that have non-empty values.Sort them alphabetically by header name.
Convert each header name to lowercase:
X-Opensearch-Nonce→x-opensearch-nonce.Remove all spaces around the colon separator:
x-opensearch-nonce : value→x-opensearch-nonce:value.Join all header-value pairs with
\n, and append\nafter the last pair.
Before/after example:
Before: X-Opensearch-Nonce : 1551089397451704
After: x-opensearch-nonce:1551089397451704\nIf the request has noX-Opensearch-*headers, omitCanonicalizedOpenSearchHeadersfrom the string-to-sign entirely — do not include an empty line or a trailing\nin its place.
When adding these headers to the actual HTTP request, use the original mixed-case names (X-Opensearch-Nonce), not the lowercase form used in signature calculation.
Step 3: Build CanonicalizedResource
Search requests:
CanonicalizedResource= path +?+ query stringPush-data requests:
CanonicalizedResource= path only
Build the path
Encode the raw path string, then replace any %2F back to /. Replace app_schema_demo with your application name and tab with your table name.
| Request type | Path |
|---|---|
| Search | /v3/openapi/apps/<app-name>/search |
| Suggestion search | /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)
Drop parameters whose values are empty.
Sort the remaining parameters alphabetically — first by key, then by value.
Encode each key and value per RFC 3986, then join them with
=.Join all encoded key-value pairs with
&.Concatenate clauses within the
queryparameter using&&before URL-encoding.
Example: The following query string:
fetch_fields=name&query=query%3Dname%3A%27%E6%96%87%E6%A1%A3%27%26%26sort%3Did%26%26config%3Dformat%3Afulljsondecodes to:
fetch_fields=name
query=query=name:'document'&&sort=id&&config=format:fulljsonThe full CanonicalizedResource for this example:
/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 4: Compute the signature
Use HMAC-SHA1 as defined in RFC 2104. Encode all strings in UTF-8. If the string-to-sign contains non-ASCII characters, encode them to UTF-8 bytes before computing the HMAC.
Pseudocode:
string_to_sign = VERB + "\n"
+ Content-MD5 + "\n"
+ Content-Type + "\n"
+ Date + "\n"
+ CanonicalizedOpenSearchHeaders # omit if no X-Opensearch-* headers
+ CanonicalizedResource
hmac_bytes = HMAC-SHA1(key=AccessKeySecret.encode("utf-8"),
msg=string_to_sign.encode("utf-8"))
Signature = base64_encode(hmac_bytes)Python 3 example:
import hmac
import base64
string_to_sign = '\n'.join([
'GET',
'', # Content-MD5 is blank for search requests
'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'),
string_to_sign.encode('utf-8'),
'sha1'
)
signature = base64.b64encode(signature_hmac.digest())Step 5: Add the Authorization header
Combine your AccessKey ID and the computed signature:
headers['Authorization'] = 'OPENSEARCH ' + '<your-access-key-id>' + ':' + signatureInclude all required headers in the HTTP request. The header values must exactly match the values used during signature calculation.
Required headers for 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 |
Required headers for push-data 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 |
Including Content-MD5, Content-Type, Date, CanonicalizedOpenSearchHeaders, and Authorization in every request reduces the chance of errors caused by missing optional headers.
End-to-end example
This example walks through a signed GET search request step by step so you can verify each intermediate value.
Input values
| Field | Value |
|---|---|
| AccessKey ID | LTAI**************** |
| AccessKey secret | yourAccessKeySecret |
| Request method | GET |
| Content-MD5 | *(blank — no request body)* |
| Content-Type | application/json |
| Date | 2019-02-25T10:09:57Z |
| X-Opensearch-Nonce | 1551089397451704 |
| Application name | app_schema_demo |
Step 2 result: CanonicalizedOpenSearchHeaders
x-opensearch-nonce:1551089397451704\nStep 3 result: 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%3AfulljsonStep 1 result: string-to-sign
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%3AfulljsonStep 4 result: signature
1P7tfE****Step 5 result: Authorization header
Authorization: OPENSEARCH LTAI****************:1P7tfE****Full request headers
Content-MD5:
Content-Type: application/json
Date: 2019-02-25T10:09:57Z
X-Opensearch-Nonce: 1551089397451704
Authorization: OPENSEARCH LTAI****************:1P7tfE****Full request URL
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%3AfulljsonRequest URL format
The full request URL is host + CanonicalizedResource. Replace <host> with the OpenSearch API endpoint for your region.
| Request type | URL |
|---|---|
| Search | http://<host>/v3/openapi/apps/<app-name>/search?<query-string> |
| Suggestion search | http://<host>/v3/openapi/apps/<app-name>/suggest/<suggestion-name>/search?hits=10&query=<encoded-query> |
| Search by application ID | http://<host>/v3/openapi/apps/<app-id> |
| Push data | http://<host>/v3/openapi/apps/<app-name>/<table-name>/actions/bulk |
For push-data requests, include the data payload in the request body.
Application schema template
The following schema defines a Standard application named app_schema_demo. Use it as a starting point for your own application.
{
"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 }
}SDK alternatives
The OpenSearch SDKs for Java, PHP, Python, and C# implement this signature method. The PHP, Python, and C# SDKs include working implementations you can reference when implementing signing in other languages.
OpenSearch does not maintain SDKs that users build from the official source code. Users are responsible for maintaining such implementations.