Skip to content

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 MethodRecommendationUse CaseRequest Authentication
Method 1: OAuth 2.1 + PKCERecommendedThird-party applications, user authorization, and access to account or trading resources on behalf of usersAuthorization: Bearer {access_token}
Method 2: Legacy API KeyCompatibleDeveloper-owned server systems, backend tasks, and compatibility with legacy integrationsX-Api-Key + Authorization: {signature_base64}

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:

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

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

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

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

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

ParameterRequiredExampleDescriptionSource
client_idYes4a8bcd69-e915-4778-9583-17ad0e9e6a80OAuth client ID that identifies the current applicationReturned by the OAuth client registration API
code_challengeYesstlSAHmH-iuYaK76djkKQpu7Jk1uAh_Dq09M_EYXDXkPKCE challenge value used to prevent an intercepted authorization code from being abusedCalculated from code_verifier as BASE64URL-ENCODE(SHA256(code_verifier))
code_challenge_methodYesS256Calculation method for code_challengeAlways pass S256
redirect_uriYeshttp://localhost:60355/callbackCallback URL after the user completes authorizationMust exactly match one of the redirect_uris submitted when registering the OAuth client
response_typeYescodeOAuth response typeAlways pass code
stateYes{random_state}Random string used to prevent CSRF attacks. It can also store business contextGenerated 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:

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

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

Your application only needs to handle it as a normal HTTP request:

  1. Listen on the /callback route of localhost:60355.
  2. Read code and state from the query.
  3. Verify that the callback state equals the value saved before authorization.
  4. After validation passes, pass code and the previously saved code_verifier to Step 3 to exchange for a Token.

Pseudocode:

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

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

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

Request example:

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

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_inValidity period of access_token, in seconds
refresh_tokenRefresh token used to obtain a new access_token after the current one expires
scopeAuthorized 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:

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

Request example:

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

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

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

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

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

AlgorithmDescription
Ed25519Directly sign the signature payload with an Ed25519 private key
RSA-SHA256Sign 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:

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

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

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

If timestamp_ms=1782357937000 and this GET request has no request body, the exact signature payload 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 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:

AlgorithmSigning Method
Ed25519Sign the signature payload directly with the Ed25519 private key
RSA-SHA256Calculate 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:

HeaderRequiredDescription
X-Api-KeyYesAppKey ID
AuthorizationYesBase64-encoded signature result. In the AppKey scenario, fill in the signature string directly; do not add the Bearer prefix
X-TimestampYesClient's current millisecond timestamp. It must match timestamp_ms in the signature payload
X-NonceYesClient-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:

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

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

Response
{"server_time_ms":"1782971427455"}

Common Conventions

  • Security identifier: {market}.{code}, such as HK.00700 or US.AAPL.
  • Time: Mostly Unix millisecond timestamps; some are second-level timestamps. Date fields use YYYY-MM-DD in the security's market timezone.
  • Ratios: Percentage values. 1.23 means 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.