Getting Started
The API is standard REST / HTTP. You can call it from any language with an HTTP client. No dedicated SDK installation is required.
API Host
- HTTP API -
https://webapi.moomoo.com - WebSocket Quote -
wss://webapi-quote.moomoo.com - WebSocket Trade -
wss://webapi-trade.moomoo.com
INFO
Most time fields are Unix millisecond timestamps, such as update_time and listing_date. Some fields are in seconds, such as wrt_maturity_date. Date fields such as data_date use YYYY-MM-DD in the security's market timezone.
Choose an Authentication Method
moomoo OpenAPI supports two authentication methods. We recommend using Method 1: OAuth 2.1 + PKCE first.
| Authentication method | Recommendation | Best for | Request authentication |
|---|---|---|---|
| Method 1: OAuth 2.1 + PKCE | Recommended | Third-party apps, user authorization, and apps that access account or trading resources on behalf of users | Authorization: Bearer {access_token} |
| Method 2: Traditional API Key | Compatible | Server-side systems, backend jobs, and compatibility with existing API Key integrations | X-Api-Key + Authorization: {signature_base64} |
Method 1: OAuth 2.1 + PKCE (Recommended)
OAuth 2.1 + PKCE is the recommended authentication method. It uses Bearer Tokens to call APIs, so you do not need to store an API private key or calculate a signature for every REST request.
Use Cases
Use this method for third-party apps, desktop apps, mobile apps, web apps, and other scenarios that require user authorization.
Step 1: Register an OAuth Client
Run the following command to register an OAuth client and obtain a client_id:
curl -X POST https://webapi.moomoo.com/oauth2/register \
-H "Content-Type: application/json" \
-d '{
"redirect_uris": ["http://localhost:60355/callback"],
"token_endpoint_auth_method": "none",
"grant_types": ["authorization_code","refresh_token"],
"response_types": ["code"],
"client_name": "My moomoo OpenAPI"
}'Response example:
{
"client_id": "4a8bcd69-e915-4778-9583-17ad0e9e6a80",
"client_id_issued_at": 1782357937,
"client_name": "My moomoo OpenAPI",
"redirect_uris": ["http://localhost:60355/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"token_endpoint_auth_method": "none",
"response_types": ["code"],
"registration_access_token": "2827551884d13cbed3ea58280b44765a56eae0cca39587b732bda239b322a35a",
"registration_client_uri": "https://webapi.moomoo.com/oauth2/register/4a8bcd69-e915-4778-9583-17ad0e9e6a80",
"scope": "quote:read quote:write trade:read trade:write accid:*",
"pkce_required": true
}Save the client_id for later steps.
Step 2: Direct the User to Authorize and Receive the Authorization Code
After obtaining the client_id, generate and temporarily store state and code_verifier. Then build the authorization URL and direct the user to open it in a browser.
code_verifier should be a high-entropy random string. code_challenge is calculated from code_verifier:
code_challenge = BASE64URL-ENCODE(SHA256(code_verifier))Store state and code_verifier in the current authorization session. They are required later for callback validation and token exchange.
Request URL:
GET https://webapi.moomoo.com/oauth2/authorize/confirmThis URL is the browser authorization page. The user signs in, selects the scopes to authorize, and confirms authorization on this page. Developers do not need to call the internal backend authorization interface directly.
Query parameters:
| Parameter | Required | Example | Description | Source |
|---|---|---|---|---|
client_id | Yes | 4a8bcd69-e915-4778-9583-17ad0e9e6a80 | OAuth client ID that identifies your app | Returned by the OAuth client registration API |
code_challenge | Yes | stlSAHmH-iuYaK76djkKQpu7Jk1uAh_Dq09M_EYXDXk | PKCE challenge used to prevent misuse if the authorization code is intercepted | Calculated from code_verifier as BASE64URL-ENCODE(SHA256(code_verifier)) |
code_challenge_method | Yes | S256 | Method used to calculate code_challenge | Always pass S256 |
redirect_uri | Yes | http://localhost:60355/callback | Callback URL after the user completes authorization | Must exactly match one of the redirect_uris used when registering the OAuth client |
response_type | Yes | code | OAuth response type | Always pass code |
state | Yes | {random_state} | Random string used to prevent CSRF attacks. You can also use it to preserve business context | Generated by the developer and validated during callback handling |
Example authorization URL after encoding and assembling the parameters. It is split across lines for readability. Remove line breaks and indentation before opening it in a browser.
https://webapi.moomoo.com/oauth2/authorize/confirm?
client_id=4a8bcd69-e915-4778-9583-17ad0e9e6a80&
code_challenge=stlSAHmH-iuYaK76djkKQpu7Jk1uAh_Dq09M_EYXDXk&
code_challenge_method=S256&
redirect_uri=http%3A%2F%2Flocalhost%3A60355%2Fcallback&
response_type=code&
state={random_state}Authorization callback:
redirect_uri is an HTTP endpoint provided by your app. It is not an OpenAPI endpoint. Using http://localhost:60355/callback as an example, after the user completes authorization, the browser sends a GET request to your app with code and state in the query string:
http://localhost:60355/callback?code={authorization_code}&state={state}Handle it like a normal HTTP request:
- Listen on
localhost:60355for the/callbackroute. - Read
codeandstatefrom the query string. - Check that the returned
statematches the value saved before authorization. - If the check passes, use
codeand the savedcode_verifierin Step 3 to exchange for tokens.
Pseudocode:
on GET /callback:
code = query["code"]
state = query["state"]
if state != saved_state:
return "invalid state"
exchange code and saved_code_verifier for tokensauthorization_code can only be used to exchange for tokens. It is not an access token for OpenAPI calls. The authorization code expires in 5 minutes and can only be used successfully once. Exchange it for tokens immediately after receiving the callback. If it expires or is reused, start the authorization flow again.
Step 3: Exchange the Authorization Code for Tokens
Body parameters:
| Parameter | Required | Example | Description |
|---|---|---|---|
grant_type | Yes | authorization_code | Always pass authorization_code |
code | Yes | {authorization_code} | Authorization code returned in the callback |
client_id | Yes | 4a8bcd69-e915-4778-9583-17ad0e9e6a80 | OAuth client ID |
redirect_uri | Yes | http://localhost:60355/callback | Must exactly match the redirect_uri used in the authorization URL |
code_verifier | Yes | {code_verifier} | Original random string used to generate code_challenge |
Request example:
curl -X POST https://webapi.moomoo.com/oauth2/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "code={authorization_code}" \
-d "client_id=4a8bcd69-e915-4778-9583-17ad0e9e6a80" \
-d "redirect_uri=http://localhost:60355/callback" \
-d "code_verifier={code_verifier}"Response example:
{
"access_token": "xxxx",
"token_type": "Bearer",
"expires_in": 7200,
"refresh_token": "yyyy",
"scope": "quote:read trade:read accid:123456"
}Response fields:
| Field | Description |
|---|---|
access_token | Access token used to call OpenAPI |
token_type | Fixed value: Bearer |
expires_in | Lifetime of access_token, in seconds |
refresh_token | Refresh token used to obtain a new access_token after the current one expires |
scope | Scopes authorized by the user, separated by spaces |
Step 4: Refresh the Access Token
When access_token expires, use refresh_token to obtain a new access_token.
Body parameters:
| Parameter | Required | Example | Description |
|---|---|---|---|
grant_type | Yes | refresh_token | Always pass refresh_token |
refresh_token | Yes | {refresh_token} | Refresh token returned when exchanging tokens |
client_id | Yes | 4a8bcd69-e915-4778-9583-17ad0e9e6a80 | OAuth client ID |
Request example:
curl -X POST https://webapi.moomoo.com/oauth2/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=refresh_token" \
-d "refresh_token={refresh_token}" \
-d "client_id=4a8bcd69-e915-4778-9583-17ad0e9e6a80"The flow above is the main Public Client + PKCE flow. If you use a Confidential Client with token_endpoint_auth_method=client_secret_post, include client_secret in the request body when refreshing.
Response example:
{
"access_token": "zzzz",
"token_type": "Bearer",
"expires_in": 7200,
"scope": "quote:read trade:read accid:123456"
}The refresh request does not rotate refresh_token. Continue storing the original refresh_token securely.
Step 5: Call a REST API
After obtaining access_token, include the Bearer Token in the Authorization request header for REST API calls:
Authorization: Bearer {access_token}You do not need to calculate a request signature or pass client_id again in REST API requests. The server identifies the user, OAuth client, and authorized scopes from access_token, then checks whether the token has the required permissions for the requested API.
The following example queries the Hong Kong market trading calendar:
curl -X GET "https://webapi.moomoo.com/api/v1.0/quote/trading-days?market=HK&start=2025-12-22&end=2025-12-26" \
-H "Authorization: Bearer {access_token}"Parameter description:
| Parameter | Position | Example | Description |
|---|---|---|---|
market | Query | HK | Market prefix |
start | Query | 2025-12-22 | Start date in yyyy-MM-dd format |
end | Query | 2025-12-26 | End date in yyyy-MM-dd format |
If access_token expires or is invalid, refresh it with refresh_token and retry. If the response indicates insufficient permission, check whether the scopes authorized by the user cover the requested API.
OAuth Advantages
- No need to store an API private key
- No need to calculate a signature for every REST request
- Token-based authorization is better suited for user authorization in third-party apps
Token Security
Store OAuth Tokens securely in your application, such as in an encrypted file or a secure keychain. Do not store them in environment variables.
Method 2: Traditional API Key (Compatible)
Traditional API Key authentication is mainly used for compatibility with existing server-side integrations. Before each REST API call, sign the request content with your private key.
Use Cases
Use this method for server-side systems, scheduled backend jobs, internal tools, and other scenarios where the developer manages the key material directly.
Step 1: Create an AppKey
Go to https://open.moomoo.com/dashboard, open User Center, create an AppKey, and upload the public key. When creating the AppKey, choose a signature algorithm and keep the corresponding private key secure locally.
Traditional API Key authentication uses asymmetric key signatures. When creating an AppKey, choose a signature algorithm and upload the corresponding public key. When calling APIs, the client signs the request content with the local private key. The server uses the AppKey to find the public key and algorithm, then verifies the signature.
Supported signature algorithms:
| Algorithm | Description |
|---|---|
Ed25519 | Sign the signing string directly with an Ed25519 private key |
RSA-SHA256 | Sign the signing string with an RSA private key using PKCS#1 v1.5 + SHA256 |
Private Key Security
The private key should only be stored in your own secure environment. Do not upload it to the platform, commit it to a code repository, or write it in plaintext to logs or configuration files.
Step 2: Construct the Signature
Before each REST API call, sign the current request content with the private key corresponding to the AppKey. The signing string consists of 5 fields joined by newline characters \n:
{timestamp_ms} + "\n" +
{http_method} + "\n" +
{request_path} + "\n" +
{query_string} + "\n" +
{body_part}Do not omit the newline separators. Even if query_string or body_part is empty, keep the field position and the newline separators.
Field description:
| Field | Description |
|---|---|
timestamp_ms | Current millisecond timestamp, matching the X-Timestamp request header |
http_method | HTTP method in uppercase, such as GET or POST |
request_path | URL path without domain or query parameters, such as /api/v1.0/quote/trading-days |
query_string | Raw query string in the final request, without the leading ?. Use an empty string if there are no query parameters |
body_part | Lowercase hex SHA256 digest of the raw request body bytes. Use an empty string if there is no request body |
The signature must be based on the final request that will be sent. The parameter order and URL encoding in query_string must exactly match the actual request. body_part must be calculated from the raw request body bytes. Do not reformat JSON before calculating the digest.
Using the Hong Kong market trading calendar request as an example:
GET https://webapi.moomoo.com/api/v1.0/quote/trading-days?market=HK&start=2025-12-22&end=2025-12-26If timestamp_ms=1782357937000, and the GET request has no request body, the exact signing string is:
1782357937000\nGET\n/api/v1.0/quote/trading-days\nmarket=HK&start=2025-12-22&end=2025-12-26\nExpanded by line:
1782357937000
GET
/api/v1.0/quote/trading-days
market=HK&start=2025-12-22&end=2025-12-26
<empty body_part>The final \n joins query_string and the empty body_part. After signing, Base64-encode the signature bytes and use the result as the value of the Authorization request header.
Algorithm usage:
| Algorithm | Signing method |
|---|---|
Ed25519 | Sign the signing string directly with the Ed25519 private key |
RSA-SHA256 | First calculate SHA256 of the signing string, then sign with the RSA private key using PKCS#1 v1.5 |
Step 3: Call a REST API
When calling a REST API, include the following authentication information:
| Header | Required | Description |
|---|---|---|
X-Api-Key | Yes | AppKey ID |
Authorization | Yes | Base64-encoded signature. For AppKey authentication, use the signature string directly and do not add the Bearer prefix |
X-Timestamp | Yes | Current millisecond timestamp from the client. It must match timestamp_ms in the signing string |
X-Nonce | Yes | Client-generated random string used to prevent replay. Only letters, digits, underscores, and hyphens are supported. Length: 1-64 |
The following example calls the trading calendar API with AppKey authentication:
curl -X GET "https://webapi.moomoo.com/api/v1.0/quote/trading-days?market=HK&start=2025-12-22&end=2025-12-26" \
-H "X-Api-Key: {app_key}" \
-H "X-Timestamp: {timestamp_ms}" \
-H "X-Nonce: {nonce}" \
-H "Authorization: {signature_base64}"{timestamp_ms} must be the current millisecond timestamp and must match the timestamp_ms used in the signing string in Step 2. {signature_base64} is the Base64-encoded signature result. The server uses X-Api-Key to find the uploaded public key and signature algorithm, then verifies the same signing string.
If the offset between the client timestamp and the server timestamp exceeds the threshold (5 seconds by default), the API returns the error code -12006. The client can obtain the server timestamp through the following API:
curl -X GET https://webapi.moomoo.com/api/v1.0/server-time
Response
{"server_time_ms":"1782971427455"}General Conventions
- Security identifier:
{market}.{code}, such asHK.00700orUS.AAPL. - Time: Mostly Unix millisecond timestamps, with some fields in seconds. Date fields use
YYYY-MM-DDin the security's market timezone. - Ratios: Percentage values. For example,
1.23means 1.23%. - Pagination: List endpoints use
next_key/limit. See Pagination for details.
Next Steps
- Rate Limit - Quotas and back-off strategy.
- Quote API - Browse full quote API documentation.
- Trading API - Browse full trading API documentation.