1. MinGlobal External API (v1)
MIN-Global API
  • MinGlobal External API (v1)
    • 01. Overview and Quick Start
      • Step 1. Check API Reachability (no key needed)
      • Step 2. Your First Signed Request
    • 02. Authentication and Request Signing
    • 03. Spot Trading Explained
    • 04. Futures Trading Explained
    • 05. Data Formats and Conventions
    • 06. Errors and Troubleshooting
    • 07. Rate Limits, Permissions and Known Limits
    • 08. Market Data API (public, no key needed)
      • List Spot Trading Pairs
      • Get All Spot Tickers
      • Get Spot Candlesticks (Klines)
      • Get Spot Order Book (Depth)
      • Get Recent Spot Trades
      • List Futures Contracts
      • Get All Futures Tickers
      • Get Futures Candlesticks (Klines)
      • Get Futures Order Book (Depth)
      • Get Recent Futures Trades
    • 09. Wallets and Balances API
      • Get All Balances
      • Get Balances by Coin
    • 10. Assets and Networks API
      • List Supported Coins
      • List Networks for a Coin
    • 11. Spot Trading API
      • Place Spot Order (Limit Buy)
      • Place Spot Order (Market Sell)
      • List Your Spot Orders
      • Get One Spot Order
      • Cancel Spot Order
    • 12. Futures Trading API
      • Get Futures Margin Balance
      • Open Futures Position (Limit Long)
      • Close Futures Position (Reduce Only)
      • List Open Futures Orders
      • Get Futures Order History
      • Get One Futures Order
      • Cancel Futures Order
  1. MinGlobal External API (v1)

02. Authentication and Request Signing

Authentication and Request Signing#

Read this when you are writing your own client. If you only want to explore the API in Postman, the collection already does all of this for you and you can skip ahead.

The idea in one paragraph#

Instead of sending a password, you send a fingerprint of your request. You take the details of the request, join them into one string in a fixed order, and run HMAC-SHA256 over that string using your secret as the key. The server rebuilds the same string from the request it received and runs the same calculation. If the two fingerprints match, the request is genuine and nothing was altered in transit. Your secret itself is never sent.

The five headers#

HeaderRequiredValue
X-MG-API-KEYYesYour public key, for example mg_7f82ab91c4d0e5f6
X-MG-TIMESTAMPYesUnix time in milliseconds at the moment you sign
X-MG-RECV-WINDOWNoHow long the request stays valid, in milliseconds. Default 5000, maximum 60000
X-MG-SIGNATUREYesThe lowercase hex HMAC-SHA256 digest
OriginYesThe single domain your key is bound to, for example https://yourdomain.com
Add Content-Type: application/json whenever you send a body, and Idempotency-Key on every POST and DELETE.

The canonical payload#

Six fields, joined with a single newline character (\n), in exactly this order:
HTTP_METHOD
REQUEST_PATH
QUERY_STRING
BODY_HASH
TIMESTAMP
RECV_WINDOW
FieldDefinition
HTTP_METHODUppercase: GET, POST or DELETE
REQUEST_PATHThe path only. No host, no query string. For example /v1/orders
QUERY_STRINGThe raw query exactly as you send it, without the leading ?. Empty string when there is none
BODY_HASHLowercase hex SHA-256 of the raw request body bytes. With no body, use the SHA-256 of the empty string: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
TIMESTAMPThe X-MG-TIMESTAMP value, copied exactly
RECV_WINDOWThe X-MG-RECV-WINDOW value, copied exactly. If you do not send the header, this field is the empty string
Then:
X-MG-SIGNATURE = hex( HMAC-SHA256( key = apiSecret, message = canonicalPayload ) )

Five rules that cause almost every failure#

1.
Sign exactly what you send. Build your body string once, hash that string, and transmit that same string. If you hash a pretty-printed object and send a compact one, the bytes differ and the signature will never match.
2.
Do not tidy up the query string. No sorting, no re-encoding, no changing the case. The server rebuilds the raw string it received. If you sort your parameters and the server does not, the two disagree.
3.
Leaving out X-MG-RECV-WINDOW means signing an empty field, not signing 5000. The rule is literally "sign what you send".
4.
Content-Type: application/json is mandatory when there is a body. The server only reads raw bytes for requests it recognizes as JSON. Without this header your body is never read, the server hashes the empty string, and you get INVALID_SIGNATURE even though your signing code is perfect. This is the most confusing failure in the whole API.
5.
Use milliseconds, not seconds. A seconds-precision timestamp reads as roughly 55 years in the past and fails as INVALID_TIMESTAMP.

The Origin header#

Every key is bound to exactly one domain, chosen when you create the key. "No restriction" is not an option.
The comparison is on the canonical scheme://host[:port]. The default port is dropped, so https://x.example and https://x.example:443 are the same. Everything else differs: http:// is not https://, and https://api.x.example is not https://x.example.
There are no wildcards.
A request that carries neither Origin nor Referer is refused. "Cannot tell" never means "allow".
localhost is the one deliberate exception, so you can develop locally.
Your backend must set this header itself. Browsers add it automatically, and browsers are exactly the callers this API refuses. No server-side HTTP library adds it for you, so set it explicitly from your configuration.

The Origin header is not signed, and it is not authentication#

It is a misconfiguration control. It stops a key pasted into the wrong project or the wrong deployment from working, and it makes traffic traceable to your site. Any non-browser client can send whatever Origin it likes, so it is not a defense against someone who already holds your key. Your signature, the freshness window and the rate limit are what protect the credential.
Practically: send it, keep it identical to what you registered, and do not add it to the canonical payload.

What the server checks, and in what order#

The first failing step is the one you are told about, so knowing the order tells you which problem you actually have:
1.
Key exists, or 401 INVALID_API_KEY
2.
Key is active, or 403 API_KEY_DISABLED
3.
Origin matches, or 403 INVALID_ORIGIN
4.
Timestamp is fresh, or 401 INVALID_TIMESTAMP
5.
Signature has not been used before, or 401 REPLAYED_REQUEST
6.
Signature verifies, or 401 INVALID_SIGNATURE
7.
Key holds the permission for this route, or 403 API_PERMISSION_DENIED
8.
Account is KYC verified, or 403 KYC_REQUIRED
Because the origin is checked before the signature, a wrong origin fails the same way whether your signing is right or wrong. Rule it out first when a client that used to work suddenly stops.

Freshness and replay protection#

Both the timestamp and the receive window are inside the signature, so a captured request cannot be re-dated or given a longer life without breaking its own signature.
Freshness is checked in both directions: |serverTime - timestamp| must be less than or equal to recvWindow, and recvWindow itself may not exceed 60000 ms. Two consequences follow:
Post-dating a request buys you nothing. A future timestamp beyond the window fails exactly like a stale one. The largest clock error that can ever be tolerated is 60 seconds.
You cannot pre-sign requests. Sign at the moment you send. A signed request is valid for at most 60 seconds and can be used only once.
Each signature may be used once. Sending the same signature twice inside its window returns 401 REPLAYED_REQUEST. When you retry a failed request, always sign it again with a fresh timestamp. Never resend the identical signed bytes.
One quirk worth knowing while debugging: replay is reserved before the signature is verified, so resending an identical wrong signature reports REPLAYED_REQUEST rather than INVALID_SIGNATURE. Re-sign every attempt and you will never see this.

Verified test vectors#

Check your implementation against these three before you write anything else. Getting the canonical payload right is the only genuinely hard part of this integration, and the symptom of getting it wrong is an unhelpful 401 INVALID_SIGNATURE on every call.
All three use:
apiSecret        = mgs_EXAMPLEONLYdoNotUse_0123456789abcdefghij
X-MG-TIMESTAMP   = 1755500000000
X-MG-RECV-WINDOW = 5000
Vector A. GET /v1/wallets/balance, no query, no body
canonical = "GET\n/v1/wallets/balance\n\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\n1755500000000\n5000"
signature = 0845ad6a032a297fcc8700949f691ba926da6203dfd4c5edc79249924c277285
Vector B. GET /v1/orders?limit=10
canonical = "GET\n/v1/orders\nlimit=10\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\n1755500000000\n5000"
signature = 42486da25871ee591aa91ae6d68a27945e6d3da50a6373f590bb4e601a3ecb82
Vector C. POST /v1/orders with a body
body      = {"symbol":"BTC-USDT","side":"BUY","type":"LIMIT","quantity":"0.001","price":"60000"}
bodyHash  = ab789dd3cdd8accdd479cf45699c53831a953436d3f018171b6b42de3e76d1a1
canonical = "POST\n/v1/orders\n\nab789dd3cdd8accdd479cf45699c53831a953436d3f018171b6b42de3e76d1a1\n1755500000000\n5000"
signature = 655ad61a4dc2a1c8bf437313deba665a03c044b1698d545dae46a1ae128e37c3
How to read the results: if A passes but C fails, your body hashing or your Content-Type header is wrong. If A fails, check that you are hashing the empty string for BODY_HASH rather than leaving that field blank.
Origin appears in none of these vectors because it is not signed. A client can reproduce all three signatures perfectly and still be refused 403 INVALID_ORIGIN if it forgets the header against the live API.

Reference implementation (Node.js, no dependencies)#

Reference implementation (Python)#

How this collection signs for you#

The collection runs a pre-request script that builds the canonical payload from the request you are about to send, signs it with your api_secret, and injects all five headers. It skips anything under /v1/market/, because those routes are public.
It resolves {{variables}} in the path, the query and the body before hashing, so the bytes it signs are the bytes Postman transmits. It also generates a fresh Idempotency-Key on every POST and DELETE.
The script is verified against the three test vectors above. You can read it under the collection's Scripts tab.
Apidog and other tools: Apidog does not run Postman sandbox scripts identically. If you import this collection there, either port the script into Apidog's own pre-request script feature, or compute the signature yourself using the rules on this page.
Modified at 2026-09-02 09:50:41
Previous
Step 2. Your First Signed Request
Next
03. Spot Trading Explained
Built with