Getting Started
This API uses standard REST / HTTP. You can call it from any programming language with an HTTP client; no dedicated SDK is required.
API Host
- HTTP API -
https://webapi.futunn.com - WebSocket Quote -
wss://webapi-quote.futunn.com/ws - WebSocket Trade -
wss://webapi-trade.futunn.com/ws
INFO
Most time fields are Unix millisecond timestamps, such as update_time and listing_date; some are second-level timestamps, 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
Futunn OpenAPI supports two authentication methods. Method 1, OAuth 2.1 + PKCE, is recommended.
| Authentication Method | Recommendation | Use Case | Request Authentication |
|---|---|---|---|
| Method 1: OAuth 2.1 + PKCE | Recommended | Third-party applications, user authorization, and access to account or trading resources on behalf of users | Authorization: Bearer {access_token} |
| Method 2: Legacy API Key | Compatible | Developer-owned server systems, backend tasks, and compatibility with legacy 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 a Bearer Token to call APIs, so you do not need to store an API private key or calculate a signature for every REST request.
Use Cases
This method is suitable for third-party applications, desktop applications, mobile applications, web applications, 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.futunn.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 Futunn OpenAPI"
}'Response example:
{
"client_id": "4a8bcd69-e915-4778-9583-17ad0e9e6a80",
"client_id_issued_at": 1782357937,
"client_name": "My Futunn 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.futunn.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 subsequent steps.
Step 2: Redirect the User for Authorization and Obtain an Authorization Code
After obtaining the client_id, generate and temporarily store state and code_verifier, then build the authorization URL and guide the user to open it in a browser to complete authorization.
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 temporarily in the current authorization session. They will be used later to validate the callback and exchange the authorization code for a Token.
Request URL:
GET https://webapi.futunn.com/oauth2/authorize/confirmThis URL is the browser authorization page. The user completes login, selects the authorization scope, and grants authorization on this page. Developers do not need to call internal backend authorization APIs directly.
Query parameters:
| Parameter | Required | Example | Description | Source |
|---|---|---|---|---|
client_id | Yes | 4a8bcd69-e915-4778-9583-17ad0e9e6a80 | OAuth client ID that identifies the current application | Returned by the OAuth client registration API |
code_challenge | Yes | stlSAHmH-iuYaK76djkKQpu7Jk1uAh_Dq09M_EYXDXk | PKCE challenge value used to prevent an intercepted authorization code from being abused | Calculated from code_verifier as BASE64URL-ENCODE(SHA256(code_verifier)) |
code_challenge_method | Yes | S256 | Calculation method for 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 submitted 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. It can also store business context | Generated by the developer and validated during callback handling |
Example authorization URL after concatenation and encoding. It is split across lines for readability; remove line breaks and indentation when opening it:
https://webapi.futunn.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 application, 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 application and includes code and state in the query parameters:
http://localhost:60355/callback?code={authorization_code}&state={state}Your application only needs to handle it as a normal HTTP request:
- Listen on the
/callbackroute oflocalhost:60355. - Read
codeandstatefrom the query. - Verify that the callback
stateequals the value saved before authorization. - After validation passes, pass
codeand the previously savedcode_verifierto Step 3 to exchange for a Token.
Pseudocode:
on GET /callback:
code = query["code"]
state = query["state"]
if state != saved_state:
return "invalid state"
use code and saved_code_verifier to exchange for a Tokenauthorization_code can only be used to exchange for a Token; it is not an access token for OpenAPI calls. The authorization code is valid for 5 minutes and can only be used successfully once. Exchange it for a Token immediately after receiving the callback. If it expires or is reused, start authorization again.
Step 3: Exchange the Authorization Code for a Token
Body parameters:
| Parameter | Required | Example | Description |
|---|---|---|---|
grant_type | Yes | authorization_code | Always pass authorization_code |
code | Yes | {authorization_code} | Authorization code returned in the authorization 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 in the authorization URL |
code_verifier | Yes | {code_verifier} | Original random string used to generate code_challenge |
Request example:
curl -X POST https://webapi.futunn.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 | Validity period of access_token, in seconds |
refresh_token | Refresh token used to obtain a new access_token after the current one expires |
scope | Authorized scopes. Multiple scopes are 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 for the Token |
client_id | Yes | 4a8bcd69-e915-4778-9583-17ad0e9e6a80 | OAuth client ID |
Request example:
curl -X POST https://webapi.futunn.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 for a Public Client + PKCE. If you use a Confidential Client with token_endpoint_auth_method=client_secret_post, you must also pass client_secret in the Body when refreshing.
Response example:
{
"access_token": "zzzz",
"token_type": "Bearer",
"expires_in": 7200,
"scope": "quote:read trade:read accid:123456"
}The refresh_token is not rotated during refresh. Continue storing the original refresh_token securely.
Step 5: Call a REST API
After obtaining access_token, subsequent REST API requests only need to include the Bearer Token in the Authorization request header:
Authorization: Bearer {access_token}You do not need to calculate a request signature or pass client_id in REST API requests. The server identifies the user, OAuth client, and authorization scope from access_token, then validates them against the permissions required by the current API.
The following example queries the Hong Kong trading calendar:
curl -X GET "https://webapi.futunn.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 permissions, confirm that the user's authorized scope covers the current API.
OAuth Benefits
- 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 scenarios where users authorize third-party applications
Token Security
OAuth Tokens should be stored securely in the application, such as in an encrypted file or secure keychain. Do not store them in environment variables.
Method 2: Legacy API Key (Compatible)
The legacy API Key method is mainly used for compatibility with existing server-side integrations. Before each REST API call, you need to sign the request content with a private key.
Use Cases
This method is suitable for developer-owned server systems, scheduled backend tasks, internal tools, and other scenarios where the developer manages the key.
Step 1: Create an AppKey
Log in to https://open.futunn.com/dashboard, go to User Center, create an AppKey, and upload your public key. When creating an AppKey, select the signing algorithm and securely store the corresponding private key locally.
The legacy API Key method uses asymmetric key signature authentication. When creating an AppKey, select the signing algorithm and upload the corresponding public key. When the client calls an API, it signs the request content with the local private key. The server looks up the public key and algorithm by AppKey, then verifies the signature.
Currently supported signing algorithms:
| Algorithm | Description |
|---|---|
Ed25519 | Directly sign the signature payload with an Ed25519 private key |
RSA-SHA256 | Sign the signature payload 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: Build the Signature
Before each REST API call, use the private key corresponding to the AppKey to sign the request content. The signature payload consists of 5 fields joined by line breaks \n, equivalent to:
{timestamp_ms} + "\n" +
{http_method} + "\n" +
{request_path} + "\n" +
{query_string} + "\n" +
{body_part}Do not omit the line breaks between fields. Even when query_string or body_part is empty, keep the corresponding field position and the \n separators.
Field description:
| Field | Description |
|---|---|
timestamp_ms | Current millisecond timestamp, corresponding to the X-Timestamp request header |
http_method | HTTP method in uppercase, such as GET or POST |
request_path | URL path without domain name or query parameters, such as /api/v1.0/quote/trading-days |
query_string | Original query string in the final request, without the leading ?. Pass an empty string when there are no query parameters |
body_part | Lowercase hexadecimal SHA256 digest of the raw request body bytes. Pass an empty string when there is no request body |
The signature must be based on the final request content. The parameter order and URL encoding of 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 it.
Example request for querying the Hong Kong trading calendar:
GET https://webapi.futunn.com/api/v1.0/quote/trading-days?market=HK&start=2025-12-22&end=2025-12-26If timestamp_ms=1782357937000 and this GET request has no request body, the exact signature payload 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 connects 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 signature payload directly with the Ed25519 private key |
RSA-SHA256 | Calculate SHA256 for the signature payload, then sign it with the RSA private key using PKCS#1 v1.5 |
Step 3: Call a REST API
REST API calls must include the following authentication information:
| Header | Required | Description |
|---|---|---|
X-Api-Key | Yes | AppKey ID |
Authorization | Yes | Base64-encoded signature result. In the AppKey scenario, fill in the signature string directly; do not add the Bearer prefix |
X-Timestamp | Yes | Client's current millisecond timestamp. It must match timestamp_ms in the signature payload |
X-Nonce | Yes | Client-generated random string used to prevent replay attacks. Only letters, digits, underscores, and hyphens are supported. Length: 1-64 |
The following example calls the trading calendar API with the AppKey method:
curl -X GET "https://webapi.futunn.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 timestamp_ms in the signature payload from Step 2. {signature_base64} is the Base64-encoded signature result from Step 2. The server looks up the uploaded public key and signing algorithm by X-Api-Key, then verifies the signature using the same signature payload.
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.futunn.com/api/v1.0/server-time
Response
{"server_time_ms":"1782971427455"}Common Conventions
- Security identifier:
{market}.{code}, such asHK.00700orUS.AAPL. - Time: Mostly Unix millisecond timestamps; some are second-level timestamps. Date fields use
YYYY-MM-DDin the security's market timezone. - Ratios: Percentage values.
1.23means 1.23%. - Pagination: List endpoints use
next_key/limit. See Pagination.
Next Steps
- Rate Limit - Quotas and back-off strategy.
- Quote API - Browse the full quote API documentation.
- Trading API - Browse the full trading API documentation.