Skip to content

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 methodRecommendationBest forRequest authentication
Method 1: OAuth 2.1 + PKCERecommendedThird-party apps, user authorization, and apps that access account or trading resources on behalf of usersAuthorization: Bearer {access_token}
Method 2: Traditional API KeyCompatibleServer-side systems, backend jobs, and compatibility with existing API Key integrationsX-Api-Key + Authorization: {signature_base64}

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:

bash
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:

json
{
  "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:

text
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:

text
GET https://webapi.moomoo.com/oauth2/authorize/confirm

This 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:

ParameterRequiredExampleDescriptionSource
client_idYes4a8bcd69-e915-4778-9583-17ad0e9e6a80OAuth client ID that identifies your appReturned by the OAuth client registration API
code_challengeYesstlSAHmH-iuYaK76djkKQpu7Jk1uAh_Dq09M_EYXDXkPKCE challenge used to prevent misuse if the authorization code is interceptedCalculated from code_verifier as BASE64URL-ENCODE(SHA256(code_verifier))
code_challenge_methodYesS256Method used to calculate code_challengeAlways pass S256
redirect_uriYeshttp://localhost:60355/callbackCallback URL after the user completes authorizationMust exactly match one of the redirect_uris used when registering the OAuth client
response_typeYescodeOAuth response typeAlways pass code
stateYes{random_state}Random string used to prevent CSRF attacks. You can also use it to preserve business contextGenerated 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.

text
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:

text
http://localhost:60355/callback?code={authorization_code}&state={state}

Handle it like a normal HTTP request:

  1. Listen on localhost:60355 for the /callback route.
  2. Read code and state from the query string.
  3. Check that the returned state matches the value saved before authorization.
  4. If the check passes, use code and the saved code_verifier in Step 3 to exchange for tokens.

Pseudocode:

text
on GET /callback:
  code = query["code"]
  state = query["state"]

  if state != saved_state:
    return "invalid state"

  exchange code and saved_code_verifier for tokens

authorization_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:

ParameterRequiredExampleDescription
grant_typeYesauthorization_codeAlways pass authorization_code
codeYes{authorization_code}Authorization code returned in the callback
client_idYes4a8bcd69-e915-4778-9583-17ad0e9e6a80OAuth client ID
redirect_uriYeshttp://localhost:60355/callbackMust exactly match the redirect_uri used in the authorization URL
code_verifierYes{code_verifier}Original random string used to generate code_challenge

Request example:

bash
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:

json
{
  "access_token": "xxxx",
  "token_type": "Bearer",
  "expires_in": 7200,
  "refresh_token": "yyyy",
  "scope": "quote:read trade:read accid:123456"
}

Response fields:

FieldDescription
access_tokenAccess token used to call OpenAPI
token_typeFixed value: Bearer
expires_inLifetime of access_token, in seconds
refresh_tokenRefresh token used to obtain a new access_token after the current one expires
scopeScopes 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:

ParameterRequiredExampleDescription
grant_typeYesrefresh_tokenAlways pass refresh_token
refresh_tokenYes{refresh_token}Refresh token returned when exchanging tokens
client_idYes4a8bcd69-e915-4778-9583-17ad0e9e6a80OAuth client ID

Request example:

bash
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:

json
{
  "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:

text
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:

bash
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:

ParameterPositionExampleDescription
marketQueryHKMarket prefix
startQuery2025-12-22Start date in yyyy-MM-dd format
endQuery2025-12-26End 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:

AlgorithmDescription
Ed25519Sign the signing string directly with an Ed25519 private key
RSA-SHA256Sign 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:

text
{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:

FieldDescription
timestamp_msCurrent millisecond timestamp, matching the X-Timestamp request header
http_methodHTTP method in uppercase, such as GET or POST
request_pathURL path without domain or query parameters, such as /api/v1.0/quote/trading-days
query_stringRaw query string in the final request, without the leading ?. Use an empty string if there are no query parameters
body_partLowercase 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:

text
GET https://webapi.moomoo.com/api/v1.0/quote/trading-days?market=HK&start=2025-12-22&end=2025-12-26

If timestamp_ms=1782357937000, and the GET request has no request body, the exact signing string is:

text
1782357937000\nGET\n/api/v1.0/quote/trading-days\nmarket=HK&start=2025-12-22&end=2025-12-26\n

Expanded by line:

text
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:

AlgorithmSigning method
Ed25519Sign the signing string directly with the Ed25519 private key
RSA-SHA256First 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:

HeaderRequiredDescription
X-Api-KeyYesAppKey ID
AuthorizationYesBase64-encoded signature. For AppKey authentication, use the signature string directly and do not add the Bearer prefix
X-TimestampYesCurrent millisecond timestamp from the client. It must match timestamp_ms in the signing string
X-NonceYesClient-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:

bash
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:

bash
curl -X GET https://webapi.moomoo.com/api/v1.0/server-time

Response
{"server_time_ms":"1782971427455"}

General Conventions

  • Security identifier: {market}.{code}, such as HK.00700 or US.AAPL.
  • Time: Mostly Unix millisecond timestamps, with some fields in seconds. Date fields use YYYY-MM-DD in the security's market timezone.
  • Ratios: Percentage values. For example, 1.23 means 1.23%.
  • Pagination: List endpoints use next_key / limit. See Pagination for details.

Next Steps