The public Client API of this venue: the same /v1 surface this portal runs on, for your own software. The base URL is https://barriers.dev.mtf.perpetuals.com:9800.
Quickstart
From zero to your first authenticated call in three steps.
1. Create an API key
Sign in to the portal and open Settings, API keys. Create a key: name a label, choose the READ or TRADE scope and, on a two-factor account, confirm with your authenticator code.
Copy it now and store it somewhere safe: this key is shown only this once and can never be shown again.
2. Send the key
The base URL is https://barriers.dev.mtf.perpetuals.com:9800; every route sits under /v1.
Send the key on every request in one of two headers: "Authorization: Bearer bak_..." or "X-Api-Key: bak_...".
3. First calls
Market data is public, so start without any credentials at all:
Every answer is JSON. A refused call carries a stable code and a msg sentence saying why; the envelope and the full code registry are in the Conventions guide below.
Authentication
Two credentials reach the API: the signed-in session this portal uses, and the API keys you mint for your own software.
Sessions
Signing in (POST /v1/auth/login, then POST /v1/auth/login/mfa on a two-factor account) mints a bearer session token: send it as Authorization: Bearer <token> on every call. A session ends a hard 12 hours after sign-in; there is no sliding renewal. A 401 means the session is gone: sign in again.
API keys
An API key is a bearer credential of its own: bak_ followed by 43 random characters. Send it in one of the two headers named in the Quickstart; a key works until you revoke it.
Keys are created, listed and revoked under Settings, API keys only, never through the API itself: the /v1/apikeys routes are session surfaces and refuse every key.
Requests with a key share one budget per account across all your keys: 600 reads (GET) and 120 other requests (orders and every other change) per 60 seconds in a sliding window. Beyond it the router answers 429 code 4290 with "Too many requests. Please slow down and try again." until the window clears. The three-stream cap and verification gates are your account's, exactly like your signed-in session. The budget is the account's, not the key's: two keys of one account draw on the same window, and the figures your account is on are in the limits block of GET /v1/apikeys and on the Settings page (support may set tighter ones for you, never higher). When support set a source-address allowlist for your account on your request, a key presented from any other address answers 401 exactly like an unknown key. Client API v1 has no request signing: the key is a bearer credential over TLS, nothing is hashed or signed per request (the venue-style HMAC signing you may know from other APIs is not part of this one).
READ and TRADE
A READ key can read your account, markets, orders, positions and statements, and open the data stream; it cannot change anything.
A TRADE key can do everything a READ key can, and also place, replace and cancel orders, work with quote requests and barrier contracts, accept staking offers and preview margin.
A call outside the key's scope answers 403 code 4034 with the one sentence "This API key cannot use this endpoint."
Surfaces keys never reach
A key never works on sign-in, password, e-mail, two-factor and session surfaces, API key management itself, withdrawals and other money changes, verification and identity submissions, consents, GDPR requests, account closure, support requests, device and notification settings, or generating new statements (reading existing statements works with READ).
In the reference below these routes wear the Session or Session only badge; everything a key can reach wears Key: READ or Key: TRADE.
Two-factor step-up
Some sensitive actions ask for the current authenticator code even inside a valid session: a withdrawal and the creation of an API key answer 400 code 4033 with data.mfaRequired when the code is missing, and a sentence naming a wrong one. Send totpCode in the body and call again.
Key custody
Only the SHA-256 hash of a key is stored; the full key is shown once at creation and can never be shown again.
Every list shows the first 10 characters only (bak_ plus 6).
At most 10 active keys per account; revoke one to make room.
Revoking a key stops its requests right away.
Conventions
The wire rules every endpoint follows.
The envelope
Every error answers {code, msg, error}: code is a stable four-digit number, msg is a sentence you can show to a person as is, and error repeats msg for older readers. Some errors add data with structured facts (for example lockedUntil on a throttle, or the leverage figures on a 409).
An error
{
"code": 4000,
"msg": "The page size must be a whole number between 1 and 500.",
"error": "The page size must be a whole number between 1 and 500."
}
Newer routes answer {code: 0, data: ...} on success. The routes that predate the envelope keep their own body shape and only ever gain fields; the reference below shows the exact answer of every route.
A success
{
"code": 0,
"data": { "keys": [ ... ] }
}
Money and numbers
Money, prices and quantities travel as decimal strings, never as floats: money in USDC at 2 decimals, prices and quantities on their market's grid. Parse them with a decimal library, compute as decimals and send the same shape back.
Timestamps
Every timestamp is Unix milliseconds UTC. The one ISO-8601 field on the surface is the account summary's since (with a Z), and it rides beside its Unix twin sinceTs.
Paging
Paged lists take limit (a whole number 1 to 500, default 100) and a before cursor, and answer hasMore with nextBefore: pass nextBefore as the next call's before to read the older page. A malformed limit or cursor answers 400 with a sentence, never silently page one.
Rate limits
A throttled call answers 429 code 4290 in the standard envelope. Sign-in and step-up locks carry data.lockedUntil (Unix ms): wait until then; a step-up lock also signs every session out, data.signedOut. The three-streams cap of GET /v1/stream answers the same code. Requests with an API key count against the same per-account limits as your session.
Streaming
GET /v1/stream?channels=<csv> turns the polls into one server-sent-events response. Channels: tickers, orderbook:SYMBOL (at most two per stream), margin, rfq and notifications; the data of every event is the same body the matching REST route answers, so a poller and a stream reader parse one shape.
The server sends retry: 3000 first, evaluates each channel at its own cadence (tickers, orderbook and rfq every second, margin every 2 seconds, notifications every 5) and writes only on change; a comment line : hb flows every 15 seconds so a quiet stream is distinguishable from a dead one. At most three concurrent streams per account.
Keep your polling code: treat any stream failure as fall back to polling and retry the stream with your usual backoff. The stream is an optimisation, never the only transport.
Error codes
The stable code registry, with the HTTP status each code travels with. The msg beside a code is always a readable sentence; show it as is. Two codes can answer before a route runs: 4010 without a valid credential and 4038 when the account is serviced by a partner.
Code
HTTP
Meaning
4000
400
Validation: a malformed body or parameter, or a refused order, cancel or request; msg says what to fix.
4001
400
Replace: the cancel half was refused and nothing was placed; the old order stands.
4002
400
Replace: the old order was cancelled but the replacement was refused; data says what happened.
4004
404
Not found: the id or symbol does not exist or does not belong to your account.
4010
401
Not signed in: the request carries no valid credential (an unknown or revoked API key answers this too).
4011
401
The refresh token is unknown, already rotated or revoked; reusing a rotated token revokes its whole family. Sign in again.
4030
403
Verification gate: the action needs a verification level or block your account does not have; data carries action, level, requiredLevel and missingBlocks.
4031
403
Registration from your country is not accepted.
4032
403
Your e-mail address is not verified yet; data carries emailUnverified.
4033
400
Two-factor step-up: the action needs the current authenticator code; data carries mfaRequired. Send the code and call again.
4034
403
This API key cannot use this endpoint: the route is outside the key's scope or excluded for keys.
4035
403
Consent required: the TERMS re-consent gate refused a new position until you accept the current version under Settings, Privacy and consents (data carries doc, currentVersion and gateFrom); on a barrier request the same number with data.error KID_REQUIRED or RISK_DISCLOSURE_CONSENT_REQUIRED names the key information document or the Risk Disclosure to acknowledge first.
4038
403
This account is serviced by your provider: an account onboarded through a partner is serviced on the partner's channel only, so any direct request carrying its session or API key (every /v1 route, the money desk, a password reset with a token) answers this code with data.error PARTNER_CHANNEL; integrate through the partner instead.
4039
403
Profile image uploads are not available on this account (a moderation decision); contact support if you have a question.
4040
403
The pattern scanner is not available on this account (a FAIL appropriateness result while the MiFID level is not granted, or a restriction).
4090
409
The leverage change would breach your margin; data carries the after figures. Confirm with force to apply it anyway.
4091
409
The order's leverage hint is not the stored choice any more; nothing was written. Re-read your leverage and send again.
4092
409
Conflict: the verification section is locked or its state does not allow the action.
4290
429
Too many requests: a throttle, a sign-in lock, or the account's API key budget (600 reads or 120 other requests per 60 seconds by default, shared across all keys, the figures on GET /v1/apikeys); data may carry lockedUntil (Unix ms); a step-up lock also signs every session out, data.signedOut.
5000
500
The support request could not be recorded: a COMPLAINT whose complaints-register entry could not be written; nothing was created (no case, no request), send it again.
5020
502
The trading venue cannot be reached right now; try again shortly.
5030
503
A backing service is unavailable right now; try again shortly.
Endpoint reference
Access levels
Every endpoint carries one of five access badges. API keys are created under Settings, API keys; the scope matrix in the Authentication guide says exactly which routes a key reaches.
Public
No credentials: anyone can call it.
Key: READ
A signed-in session, or an API key of either scope (READ suffices).
Key: TRADE
A signed-in session, or an API key of scope TRADE; a READ key is refused with 403 code 4034.
Session
A signed-in session (bearer token). API keys do not reach this route, but it is an ordinary signed-in surface.
Session only
Only a signed-in session ever reaches it: API keys are excluded whatever their scope (sign-in and credentials, money mutations, verification writes, API key management). A key call answers 403 code 4034.
Authentication
Registration with e-mail verification, password login with an optional TOTP second step, bearer sessions, the mobile refresh-token grant (rotate on use, 90-day absolute family cap), password and e-mail changes, sessions and login history. Every error answer carries code (a stable four-digit number), msg (a readable sentence) and the same sentence under error; timestamps are Unix milliseconds UTC. API keys can NEVER reach these routes: the whole /v1/auth namespace is excluded for keys, so they are session (or public) surfaces only.
POST/v1/auth/registerPublic
Open an account with e-mail, password and country of residence
Answers 201 with {emailVerificationRequired: true} for a new account AND for an address that already holds an open account (in the second case nothing is created and the holder is told by mail, so the route is no existence oracle). A verification code is mailed to the address; verify it before signing in. The country is screened at registration: residents of blocked jurisdictions are refused with 403 code 4031.
Request body
Name
Type
Required
Description
email
string
yes
The sign-in e-mail address.
password
string
yes
The password; the policy sentence names the requirement when it is refused.
country
string
yes
ISO-2 country of residence, upper case (screened).
acceptTerms
boolean
yes
Must be true: "Please accept the terms to open an account."
marketing
boolean
no
Marketing opt-in, default false.
clientType
string
no
NATURAL (default) or LEGAL (a company registering through its representative).
Response example
{
"emailVerificationRequired": true
}
Errors
Code
HTTP
When
4000
400
Terms not accepted, a malformed e-mail address, the password policy, or a malformed country code (each with its own sentence).
4031
403
"We do not open accounts for residents of this country." (data carries the country).
4290
429
More than 10 registration attempts per minute per caller address.
curl
curl -X POST https://barriers.dev.mtf.perpetuals.com:9800/v1/auth/register -H "Content-Type: application/json" -d '{"email":"client@example.com","password":"a-strong-password","country":"DE","acceptTerms":true}'
POST/v1/auth/verify-emailPublic
Confirm the 6-digit verification code mailed at registration
Request body
Name
Type
Required
Description
email
string
yes
The registered address.
code
string
yes
The 6-digit code from the mail.
Response example
{
"ok": true
}
Errors
Code
HTTP
When
4000
400
"The verification code is not correct or has expired." (one sentence for an unknown address, a wrong code, an expired code, or a code burned by too many attempts).
4290
429
More than 20 attempts per minute per caller address.
curl
curl -X POST https://barriers.dev.mtf.perpetuals.com:9800/v1/auth/verify-email -H "Content-Type: application/json" -d '{"email":"client@example.com","code":"123456"}'
POST/v1/auth/verify-email/resendPublic
Mail a fresh verification code
Always answers {ok: true}; a new code is mailed only to an open, still unverified account, at most 3 times per 15 minutes per address.
Request body
Name
Type
Required
Description
email
string
yes
The registered address.
Response example
{
"ok": true
}
Errors
Code
HTTP
When
4290
429
More than 20 requests per minute per caller address.
curl
curl -X POST https://barriers.dev.mtf.perpetuals.com:9800/v1/auth/verify-email/resend -H "Content-Type: application/json" -d '{"email":"client@example.com"}'
POST/v1/auth/loginPublic
Sign in with e-mail and password
On success the answer carries the bearer session and the client document (the GET /v1/me shape). On a two-factor account it answers {mfaRequired: true, mfaToken, expiresAt} instead: complete the sign-in on POST /v1/auth/login/mfa within 5 minutes. With refresh: true (docs/08 revision 33, the mobile grant) the SUCCESS answer also carries refreshToken and refreshExpiresAt, a device-bound refresh family whose 90-day cap is absolute; on a two-factor account the flag rides on the second step, the step that mints the session. Two locks, both 15 minutes: five failures in 15 minutes from one caller address lock that (address, caller) pair; 50 failures from anywhere lock the address itself.
Request body
Name
Type
Required
Description
email
string
yes
The sign-in address.
password
string
yes
The password.
refresh
boolean
no
true asks for a refresh-token grant (revision 33); the web portal sends neither field.
deviceName
string
no
Names the device family, at most 64 characters, trimmed.
"Wrong e-mail address or password." (an unknown address and a wrong password read the same).
4032
403
The password is right but the address is unverified: the answer carries emailUnverified true and the verify-first sentence.
4290
429
A sign-in lock (the answer carries lockedUntil) or more than 20 attempts per minute per caller address.
curl
curl -X POST https://barriers.dev.mtf.perpetuals.com:9800/v1/auth/login -H "Content-Type: application/json" -d '{"email":"client@example.com","password":"a-strong-password"}'
POST/v1/auth/login/mfaPublic
Complete a two-factor sign-in with the authenticator code
The second step: the 5-minute mfaToken from the password step plus a current TOTP code. Five wrong codes burn the token; a code already used to sign in is refused inside its window. A dead token (expired, used, burned or retired) answers 401 with data.mfaTokenExpired true so a client falls back to the password step without matching the sentence. refresh and deviceName ride on THIS step (revision 33) because this step mints the session.
Request body
Name
Type
Required
Description
mfaToken
string
yes
The token the password step answered.
code
string
yes
The 6-digit authenticator code.
refresh
boolean
no
true asks for the refresh-token grant (revision 33).
A dead token ("This sign-in has expired. Enter your e-mail address and password again.", data.mfaTokenExpired true) or a wrong code ("The authentication code is not correct.").
4290
429
More than 20 attempts per minute per caller address.
curl
curl -X POST https://barriers.dev.mtf.perpetuals.com:9800/v1/auth/login/mfa -H "Content-Type: application/json" -d '{"mfaToken":"<from the password step>","code":"123456"}'
POST/v1/auth/refreshPublic
Rotate a refresh token into a fresh session (no bearer required)
Rotate on use (docs/08 revision 33): the presented token is marked used and superseded, the answer carries the new session (kind REFRESH in the token's family), the NEW rotated refreshToken and the UNCHANGED family cap refreshExpiresAt. Presenting a token that was already rotated or revoked is the theft signal: the whole family is revoked with every session it minted. An unknown token or an expired family answers the same 401 sentence, so the route is no oracle. A throttled call does not rotate the token.
Revokes the current session. A session minted by refresh also revokes its device family, sibling sessions included (revision 33).
Response example
{
"ok": true
}
curl
curl -X POST https://barriers.dev.mtf.perpetuals.com:9800/v1/auth/logout -H "Authorization: Bearer <sessionToken>"
POST/v1/auth/logout-allSession only
Sign out everywhere
Revokes every session of the account, refresh families included; the answer counts them.
Response example
{
"ok": true,
"revoked": 3
}
curl
curl -X POST https://barriers.dev.mtf.perpetuals.com:9800/v1/auth/logout-all -H "Authorization: Bearer <sessionToken>"
POST/v1/auth/password/changeSession only
Change the password (step-up on a two-factor account)
The current session stays; every other session is revoked and counted in the answer. On a two-factor account the change is a step-up: code must be a current TOTP code, so a stolen bearer token plus the password alone cannot re-key the account.
Request body
Name
Type
Required
Description
currentPassword
string
yes
The current password.
newPassword
string
yes
The new password (policy checked; must differ from the current one).
code
string
no
A current TOTP code; required when two-factor is enabled.
Response example
{
"ok": true,
"otherSessionsRevoked": 2
}
Errors
Code
HTTP
When
4000
400
The password policy, "The new password must differ from the current one.", "The current password is wrong.", or a wrong TOTP code ("The authentication code is not correct.", data.mfaRequired true).
4033
400
"Enter the code from your authenticator app to confirm this change." (two-factor enabled, no code sent; data.mfaRequired true).
4290
429
The step-up lock after 5 wrong authenticator codes (15 minutes, data carries lockedUntil; data.signedOut at the lock: every session is signed out).
Always answers {ok: true} (no enumeration). A reset link (30 minutes) is mailed to an open account's address at most 5 times per hour per address.
Request body
Name
Type
Required
Description
email
string
yes
The sign-in address.
Response example
{
"ok": true
}
Errors
Code
HTTP
When
4290
429
More than 20 requests per minute per caller address.
curl
curl -X POST https://barriers.dev.mtf.perpetuals.com:9800/v1/auth/password/forgot -H "Content-Type: application/json" -d '{"email":"client@example.com"}'
POST/v1/auth/password/resetPublic
Set a new password with the mailed reset token
Sets the password, revokes every session and marks the address verified (the link reached it). The portal carries the token in the reset link's query string.
Request body
Name
Type
Required
Description
token
string
yes
The token from the reset link.
newPassword
string
yes
The new password (policy checked).
Response example
{
"ok": true
}
Errors
Code
HTTP
When
4000
400
"This password reset link is not valid any more. Request a new one." or the password policy sentence.
4290
429
More than 20 requests per minute per caller address.
curl
curl -X POST https://barriers.dev.mtf.perpetuals.com:9800/v1/auth/password/reset -H "Content-Type: application/json" -d '{"token":"<from the mail>","newPassword":"new-stronger-password"}'
GET/v1/auth/sessionsSession only
List the account's live sessions
One row per live session, the calling one flagged current. Since revision 33 every row carries kind (PASSWORD or REFRESH) and deviceName (the refresh family's, null when absent).
Revokes the named session of the calling account. Revoking a session whose refresh family exists also revokes the family (device-bound: one family = one handset, revision 33).
The flag set at registration (marketing) as the account holds it now; false for an account that never set it. The {code: 0, data} envelope of the newer routes.
Sets the flag and answers the same shape as the GET. A change is recorded in the account's history; sending the value the account already holds changes nothing. No password, no authenticator code and no mail: the opt-out the registration consent promises is this call with optIn false.
Request body
Name
Type
Required
Description
optIn
boolean
yes
true to receive marketing e-mail, false to stop it.
Response example
{
"code": 0,
"data": {
"optIn": false
}
}
Errors
Code
HTTP
When
4000
400
"optIn must be true or false" (missing, or not a JSON boolean).
"Two-factor authentication is already enabled on this account."
curl
curl -X POST https://barriers.dev.mtf.perpetuals.com:9800/v1/auth/mfa/setup -H "Authorization: Bearer <sessionToken>"
POST/v1/auth/mfa/enableSession only
Enable two-factor with the first authenticator code
Request body
Name
Type
Required
Description
code
string
yes
The 6-digit code from the authenticator app.
password
string
yes
The account password.
Response example
{
"enabled": true,
"enabledAt": 1787392800000
}
Errors
Code
HTTP
When
4000
400
"The password is wrong.", "Two-factor authentication is already enabled on this account.", "Start the two-factor setup first, then enter the code from your authenticator app." or "The authentication code is not correct."
Request a sign-in address change (a code goes to the new address)
The account keeps its old address until the code mailed to the NEW address is confirmed on /v1/auth/email/confirm; the old address is told at once that a change was requested. An address that already holds an open account answers the SAME {ok, pendingEmail} but no code is issued (its holder gets a notice), so the signed-in route is no enumeration oracle either. On a two-factor account the request is a step-up (a current TOTP code in code). At most 3 changes per hour per client, counted after the password held.
Request body
Name
Type
Required
Description
newEmail
string
yes
The new sign-in address.
password
string
yes
The account password.
code
string
no
A current TOTP code; required when two-factor is enabled.
"We could not use that e-mail address.", "That is already the e-mail address of this account.", "The password is wrong." or a wrong TOTP code.
4033
400
"Enter the code from your authenticator app to confirm this change." (two-factor enabled, no code sent).
4290
429
More than 20 requests per minute per caller address, more than 3 changes per hour per client, or the step-up lock (data.lockedUntil; data.signedOut at the lock: every session is signed out).
Mint bearer API keys and drive the SAME /v1 surface programmatically (docs/08 revision 35). A key is bak_ plus a 43-character base64url value; only its SHA-256 is stored, the FULL key is shown ONCE in the create answer and the 10-character display prefix is what every list shows. Scopes: READ (every GET under the allowed namespaces plus GET /v1/stream) and TRADE (READ plus the trading mutations). Present a key as Authorization: Bearer bak_... or X-Api-Key: bak_...; an out-of-scope or excluded call answers 403 code 4034 with the one sentence "This API key cannot use this endpoint." The management routes below are session surfaces only: a key can never manage keys.
POST/v1/apikeysSession only
Create an API key (the full key is shown this once)
At most 10 ACTIVE keys per client. On a two-factor account the create is a step-up exactly like the withdrawal's: 400 code 4033 with data.mfaRequired without the code, the code sentence for a wrong one, the same wrong-code counter and the same lock, which signs every session out (429 code 4290, data.signedOut true at the lock; keys created earlier stay valid). Store the answered apiKey at once: it is never shown again.
Request body
Name
Type
Required
Description
label
string
yes
1 to 64 characters, trimmed; names the key in the list.
scope
string
yes
Exactly READ or TRADE.
totpCode
string
no
A current TOTP code; required when two-factor is enabled.
List the account's API keys, newest first, revoked included
The prefix, never the key. lastUsedAt is refreshed at most once per minute per key. Since docs/08 revision 50 the answer carries limits, the account's EFFECTIVE key budget (the published default 600 reads and 120 other requests per 60 second sliding window, or the tighter figures support set for this account; shared by every key of the account, a request beyond it answers 429 code 4290), and allowedCidrs, the source addresses or ranges your keys are accepted from ([] = every address; set by support on your request, a key presented from elsewhere answers 401 like an unknown key).
Public market data for every listed market: no credentials required (an API key of either scope may present itself, a session may ride along on market-info for the personal leverage cap). The venue's documents are normalised into stable camelCase shapes with prices and sizes as decimal strings. A venue reject answers 400 {code: 4000, msg, error, venueCode, venueMsg}; a venue transport failure answers 502 code 5020 "Market data is unavailable right now. Please try again shortly."
GET/v1/instrumentsPublic
The tradable listing
Every enabled market of the order and RFQ pipelines: families PERP, SPOT and BARRIER, minus the markets the operator disabled. STAKING markets are listed apart on GET /v1/staking/markets (a swap market has no price and no ticker).
One ticker row per listed market (the Top Movers strip)
Served from a 1 second cache. A market whose venue ticker failed is listed with null prices. A BARRIER market carries no funding: its rate fields are null and nextFundingTs 0. A SPOT market carries neither funding nor open interest: its rate fields AND openInterest are null. The 24 h statistics are derived from the venue's 1 h candles (quote volume approximates the USD turnover).
The venue's ticker document field for field inside the {code, msg, data, asOfSeq, ts} envelope. A venue reject answers 400 with the envelope (venueCode and venueMsg ride along) instead of a 200 carrying the venue's own error text.
t is the bucket open in Unix ms; partial is true on the bucket still open (a poll must not overwrite a completed candle with it). from (inclusive) and to (exclusive) bound the bucket opens; hasMore says older candles exist that this answer did not carry (page back with to = the oldest t received); oldestAvailable is the earliest bucket open the venue can serve for the interval (its window is the last 1000 intervals).
Path and query parameters
Name
In
Type
Required
Description
symbol
query
string
yes
The market symbol.
interval
query
string
no
1m, 5m, 15m, 30m, 1h, 4h or 1d; default 1m.
from
query
integer
no
Inclusive lower bound on the bucket open, Unix ms.
to
query
integer
no
Exclusive upper bound on the bucket open, Unix ms.
limit
query
integer
no
The newest N inside the bounds, 1 to 1000, default 200.
"A market symbol is required.", "The chart interval must be one of 1m, 5m, 15m, 30m, 1h, 4h or 1d.", or a from / to / limit value that is not a whole number.
Rates are FRACTIONS per interval (0.0001 = 0.01 percent), never amounts. funding is the venue's schedule block (GRID or LOCAL_DAILY with zone, snapshotLocalTime, vwapWindow, nextSettlementTs), null when the venue sends none. A BARRIER or SPOT market carries no funding: every rate field is null and the interval 0 there, whatever constant the venue's ticker repeats.
The venue instrument specification (sizing, ticks, bands, risk tiers, status) plus the broker's own data: family, display name, the leverage schedule of the asset class for every categorization, and the caller's cap and effective leverage when a session rides along (RETAIL without one). What the CLIENT pays is the commission block (feeModel "commission", bps on notional with the minimum applied once per order); the venueFees block is what the BROKER pays the venue, informational only. A BARRIER market answers fundingIntervalHours and funding null, leverageCap and leverage null with an empty leverageSchedule, barrierMaxLeverage, the market's cap, 10000 / the published barrier floor, "1000" on every listed market today (the ticket's slider, plus button, floor sentence and printed maximum follow it), the premium facts (knockOutRebateBps, strictTakeProfitPremiumBps, strictTakeProfitOffered, barrierPremiumBps), the expiry facts of an opening request as the barrierExpiry block (maxExplicitMs, the 8 h cap on an explicit barrier.expiryTs; defaultLifeMonths, the 12-month open-end default when the expiry is omitted; venueMaxLifeHours, the venue's published maximum contract life that clamps the default less 60 s, null on an older venue; the block is null on every other family) and, since revision 50, the key information documents kid and kidStrict (the normal and the strict take profit contract; each {doc, version, title, url, sha256, effectiveAt, acknowledged} or null while no version with a file is in force: url is the public PDF, doc the consent document code KID_<SYMBOL> or KID_<SYMBOL>_STRICT, acknowledged true or false with a session (an acceptance row for that version exists) and null without one; a RETAIL client fetches the PDF through GET /v1/consent-documents/{doc}/{version} and acknowledges it with POST /v1/consents/accept before the first request on the market, both null on every other family); a SPOT market answers a spot block (asset, displayDecimals, quoteCurrency, prefunded true) and no funding. unitLabel names the quantity unit when the venue's listing makes it other than one base asset unit, read from the venue's published tick ("1,000 JPY" on the two JPY markets once the venue lists them at tickSize 0.0001, null while the venue lists 1 JPY at 0.000001 and on every other market); read baseAsset when it is null.
The one cash currency of the client ledger (USDC) and the assets a client may hold through the spot markets and the staking swaps, each with display decimals, the ledger scale, the venue asset code and, since docs/08 revision 50, the registry's valuation basis (CASH on the unit of account, MARK at a market mark, PAR at 1 USDC per unit without a market price; no asset is par-valued today).
Standing trading halts, your active restrictions and your service incident notices with a session
The standing venue-wide or per-family halts for everyone; when a session rides along, the caller's own active restrictions are listed too, each with the sentence the trading routes would answer, and `incidents` carries the service incident notices we sent you by e-mail while the incident is still open or contained (the reference, the title, the status, when it was detected and when you were notified; at most five, newest first; the portal shows them as a banner). consentGate (docs/08 revision 50) is null for an anonymous caller, while the TERMS re-consent gate is off or while no gated document has a version in force; else {doc, currentVersion, gateFrom, active, message} for the document that gates (the one new positions are refused under, 403 code 4035 with the message until you accept), else for the first gated document with a version in force with active false, active meaning your re-consent is pending and the gate opened. No internal reason ever leaves the router. The portal's Trading status section on /settings reads it; the two restriction notifications link there.
The app checks at start and on foreground: a build below minBuild shows a blocking update screen (minBuild 0 = no gate); notice is a plain sentence shown as a banner, empty when unset. Public because a blocked build could not sign in to ask. features.demo says whether the demo trading account is offered on this server: false = the /v1/demo routes answer 503 code 5030 and a client hides the demo from its navigation; a router without the member offers the demo (absent = enabled).
The retail risk warning and the operator disclosure as served
The operator signpost naming PM MTF Ltd as broker and operator of the Kronos X MTF, with the link to the current conflicts of interest summary (null while none is published), and the three retail risk-warning forms (standard, abbreviated, reducedCharacter) carrying the firm's own quarterly loss figure once a compliance officer proposed it and a second officer signed it (lossPercent with the figure's quarter and its twelve-month window, fallback false), else the published range (fallback true, the figure and the dates null). The web portal renders the signpost and the conflicts link and none of the risk-warning forms (operator decision of 2026-09-22: the CFD-related warning is not required for barrier contracts); the forms stay on the wire for the mobile app and the partner integrations, which read the document themselves. The example shows the three sentence members elided: their wording is the router's own and is documented for the integrations that render it (the Partner API documentation), not on this page. Served from a 5 second cache; re-read at least daily. Public because the signpost is shown before any sign-in.
Response example
{
"legalEntity": "PM MTF Ltd",
"riskWarning": {
"standard": "<the standard form as served>",
"abbreviated": "<the abbreviated form as served>",
"reducedCharacter": "<the reduced-character form as served>",
"lossPercent": null,
"figureDate": null,
"windowFrom": null,
"windowTo": null,
"fallback": true
},
"operatorDisclosure": {
"signpost": "PM MTF Ltd provides this service as your broker and also operates the Kronos X MTF on which your orders are executed. Read our conflicts of interest summary.",
"conflictsSummaryUrl": null
},
"asOf": 1789000000000
}
One server-sent-events stream instead of many polls (docs/08 revision 34). The data of every event is the SAME body the polled REST route answers, so a consumer keeps its parsing code. The client stance is normative: keep the polling code paths, treat ANY stream failure as fall back to polling, and retry the stream with the existing backoff. The stream is an optimisation, never the only transport.
GET/v1/streamKey: READ
The scoped SSE stream: tickers, orderbook, margin, rfq, notifications
Channels: tickers (= the GET /v1/tickers answer), orderbook:SYMBOL (= the GET /v1/orderbook answer for that symbol at depth 25; at most 2 orderbook channels per stream), margin (= the GET /v1/margin answer), rfq (= the GET /v1/rfq/mine answer) and notifications (= {unread, newestId}, the bell's unread count and the newest item's id, null when there is none). The response is text/event-stream: first retry: 3000, then per event an event: line naming the channel exactly as requested and a data: line with one-line JSON. Events are coalesced server side and written ONLY ON CHANGE against the last-sent serialization per channel, each channel at its cadence: tickers, orderbook and rfq every second, margin every 2 seconds, notifications every 5 seconds. A heartbeat comment line ": hb" flows every 15 seconds so a quiet stream is distinguishable from a dead one. At most 3 concurrent streams per client, keys and sessions counted together; a revoked or expired session ends the stream from the server side (re-checked at most every 10 seconds). Credentials ride in headers, never in the URL.
Path and query parameters
Name
In
Type
Required
Description
channels
query
string
yes
Comma list of channels, e.g. tickers,orderbook:BTC-USD-PERP,margin,rfq,notifications.
The order pipeline for the perpetual and spot markets. Order types: LIMIT, MARKET, STOP_MARKET, STOP_LIMIT, TAKE_PROFIT_MARKET, TAKE_PROFIT_LIMIT (no trailing stops); times in force GTC, IOC, FOK and POST_ONLY (post-only applies to limit orders only). Conditional types carry their prices in the trigger object, never as a plain price. A take profit and stop loss are attached at placement through the order body's attach object (LIMIT and MARKET parents only, never with reduceOnly): the router places the legs as reduce-only conditional orders once the parent fills. There are no separate bracket routes; a working TP or SL leg is edited by replacing it through POST /v1/orders/replace and removed with DELETE /v1/orders/{orderId}. An ocoGroup ties the client's conditional reduce-only orders together: when one fills the router cancels the rest. The optional leverage field is the leverage hint the ticket prepared the order at; when it no longer matches the stored choice the order is refused with 409 code 4091 and nothing is written. Money is USDC with decimal strings; timestamps are Unix milliseconds UTC.
POST/v1/ordersKey: TRADE
Place an order
Places one order. reduceOnly means the order must shrink the client's position on that market (client level, never forwarded to the venue). Conditional types (STOP_MARKET, STOP_LIMIT, TAKE_PROFIT_MARKET, TAKE_PROFIT_LIMIT) need a trigger; the *_LIMIT variants carry the resting price in trigger.limitPrice. attach rides on LIMIT and MARKET parents only and needs at least one leg; for a BUY parent the take profit price must be above and the stop loss below the reference (the limit price, or the fresh mark for MARKET), the reverse for SELL. Spot markets refuse reduceOnly, attach, ocoGroup and market-type conditional buys with a sentence. A verification-gate refusal answers 403 code 4030 with the missing blocks so the caller can show the reason.
Request body
Name
Type
Required
Description
symbol
string
yes
The market symbol, e.g. BTC-USD-PERP.
side
string
yes
BUY or SELL.
type
string
yes
LIMIT, MARKET, STOP_MARKET, STOP_LIMIT, TAKE_PROFIT_MARKET or TAKE_PROFIT_LIMIT.
qty
string
yes
Quantity as a decimal string, greater than zero, on the market's lot grid.
price
string
no
LIMIT only (a market order takes no price; conditional orders price through the trigger). On the tick grid.
tif
string
no
GTC (default), IOC, FOK or POST_ONLY (POST_ONLY with LIMIT only).
reduceOnly
boolean
no
Default false; true = the order must reduce the position. Not available on spot markets.
trigger
object
no
Conditional types only: {source: LAST | MARK | INDEX, price, direction: AT_OR_ABOVE | AT_OR_BELOW, limitPrice (the *_LIMIT variants only)}.
attach
object
no
LIMIT and MARKET parents only: {takeProfit: {price, limitPrice?}, stopLoss: {price, limitPrice?}}, at least one leg; the router places the legs as reduce-only conditional orders when the parent fills.
ocoGroup
string
no
Conditional reduce-only orders only: 1 to 40 characters of letters, digits, colons, underscores and hyphens; when one order of the group fills the router cancels the others.
leverage
integer
no
The leverage hint the order was prepared at; a mismatch with the stored choice answers 409 code 4091 with nothing written.
Validation or a refused order (the sentence in msg; orderId, clOrdId and state ride along, orderId null when nothing was written).
4030
403
The verification gate refused an opening order (data: {action, level, requiredLevel, missingBlocks}).
4035
403
The TERMS re-consent gate (data: {doc, currentVersion, gateFrom}): an opening order while the acceptance of the current Terms version is pending past the gate date; a reduce-only order or one within the remaining reduce capacity passes.
4091
409
The leverage hint no longer matches the stored choice (data: {leverageSent, leverageStored}); nothing was written.
4010
401
No or unknown client identity.
4034
403
A READ key called this TRADE route: "This API key cannot use this endpoint."
5020
502
The trading venue cannot be reached.
4290
429
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
Replace an order (cancel then place under one lock)
The TP/SL editor's edit path: cancels one of the client's live orders and places its replacement, the two halves serialized per client, so a replaced stop loss is never refused because its predecessor still occupied the reduce budget. The replacement is validated BEFORE the old order is cancelled (shape, grid, OCO group, attach, gates, the leverage hint): a refusal of the replacement itself answers 400 code 4001 with the old order still standing; a replacement refused after the cancel answers 400 code 4002 and the answer says which half happened.
Request body
Name
Type
Required
Description
cancelClOrdId
string
yes
The order to cancel: its numeric id ("41") or venue reference ("BARR-41").
order
object
yes
The replacement, the same body as POST /v1/orders.
The order to cancel is not the client's: "This order was not found." Nothing happened.
4001
400
The cancel half was refused; nothing was placed, the old order stands.
4002
400
The old order was cancelled but the replacement was refused (the answer carries cancelled: true, placed: false and the reason).
4091
409
The replacement's leverage hint is stale (data: {leverageSent, leverageStored}); nothing happened.
5020
502
The cancel never reached the venue; nothing happened.
4290
429
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
Asks the venue to cancel the order. On acceptance the row moves to PENDING_CANCEL and the venue confirms the cancel through its own order channel; a second cancel of a PENDING_CANCEL order answers ok without another venue call.
Path and query parameters
Name
In
Type
Required
Description
orderId
path
integer
yes
The router's order id (the numeric part of the BARR- reference).
Response example
{
"orderId": 4182,
"clOrdId": "BARR-4182"
}
Errors
Code
HTTP
When
4004
404
Not the client's order: "This order was not found."
4000
400
The order is terminal, not at the venue yet, or the venue refused the cancel (the sentence in msg).
5020
502
The trading venue cannot be reached.
4290
429
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
The full working set, no row window, oldest first: CHECKED and SENT are on the way to the venue, ACKED and PARTIAL are in the book, PENDING_TRIGGER is a conditional the venue holds, PENDING_CANCEL is a cancel the venue accepted but has not confirmed yet. Rows carry the same order record as GET /v1/orders.
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
Every origin lists (CLIENT, CLOSE_OUT, ATTACHED; attached legs are the client's orders), newest first. Rows carry attach (the stored object, plus legs, legReject and note once the router acted), ocoGroup, parentOrderId / parentClOrdId, origin, note (a sentence the router wrote about the order), replacedBy / replaces (as clOrdIds) and, on spot orders, reservation {currency, amount, consumed, released}.
Path and query parameters
Name
In
Type
Required
Description
limit
query
integer
no
Page size, 1 to 500, default 100. A malformed value answers the envelope sentence.
before
query
string
no
The nextBefore of the previous page: an order id ("41") or its venue reference ("BARR-41").
A malformed limit or before value (the envelope sentence).
4010
401
No or unknown client identity.
4290
429
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
The client's PERP and SPOT fills, newest first by trade row id (the cursor); ts is the venue's fill time. Barrier contract events live on GET /v1/barriers and swap events on GET /v1/staking/swaps; a consumer wanting one feed merges the three. On spot fills notional is the quote cash the fill moved and asset the base asset delivered; both null elsewhere.
Path and query parameters
Name
In
Type
Required
Description
limit
query
integer
no
Page size, 1 to 500, default 100.
before
query
integer
no
The nextBefore of the previous page (a trade row id).
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
The client's leverage on a symbol: the effective leverage, the cap for the client's categorization and asset class (read live from the leverage schedule; a stored choice above a lowered cap is clamped at read time) and chosen (the stored choice, null when the client never set one).
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
Stores the client's leverage choice, validated against the cap and assessed against the account under the client's pipeline lock: a change that would start a margin call or close-out by itself is refused with 409 code 4090 and the full impact, unless force is true. The answer is the same shape as GET /v1/leverage.
Request body
Name
Type
Required
Description
symbol
string
yes
The market symbol.
leverage
integer
yes
The new leverage, 1 up to the cap.
force
boolean
no
Apply even when the change would start a margin call or close-out.
The change would breach without force (data: {wouldBreach: true, initialMarginAfter, maintenanceMarginAfter, freeMarginAfter, marginLevelAfter, riskStateAfter}); confirm with force: true to apply anyway.
4000
400
A missing symbol or leverage, a choice outside [1, cap], or no fresh mark to assess a raising change.
4004
404
"This market is not offered."
4290
429
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
The change would breach without force (the PUT route's answer shape).
4004
404
"This market is not offered."
4290
429
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
The client's positions and the Risk v2 margin picture. Positions are the fold maintained inside every execution's transaction: signed net quantity, average entry, realized P&L of the CURRENT position (it resets when a position opens from flat; lifetime figures are GET /v1/account/summary). The margin document is the one risk view: equity, margins, free margin, margin level, risk state, the per-position figures and the open barrier contracts' share of the picture. The preview answers the pre-trade decision and the ticket's numbers without placing anything.
GET/v1/positionsKey: READ
Open positions
One row per market with a nonzero net position: signed netQty, average entry, netCost (avg x qty, kept for older readers) and the realized P&L of the current position. Live valuation (marks, unrealized P&L, liquidation estimates) is GET /v1/margin.
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
The fills that realized P&L (a partial or full close), newest first by trade row id. Scope: PERP closes including venue closes; barrier settlements are on GET /v1/barriers and the account overview's realizedByFamily counts them.
Path and query parameters
Name
In
Type
Required
Description
limit
query
integer
no
Page size, 1 to 500, default 100.
before
query
integer
no
The nextBefore of the previous page (a trade row id).
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
The whole Risk v2 picture: equity, cash, unrealized P&L, reserved (USDC held by open spot buys, inside equity, outside freeMargin), initial and maintenance margin, position and order margin, free margin, margin level, risk state (NORMAL, MARGIN_CALL, CLOSE_OUT), the per-position figures (mark, freshness, ROI, leverage, estimated liquidation price) and the barrier block: barrierMargin (inside initialMargin), barrierUnrealizedPnl (inside unrealizedPnl and equity), pendingAcceptMargin, closeOutEquity (the conservative equity the close-out test compares, barrier profits excluded) and the open contracts' live rows.
"The margin service is unavailable right now. Please try again shortly."
4290
429
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
The pre-trade decision and the ticket's numbers, at the stored leverage or at the body's leverage (checked against the cap, never persisted; the answer echoes the leverage it was computed at). Nothing is placed or written. The spot-only refusals of the order route answer the same sentence here, so the preview never accepts a body POST /v1/orders refuses. freeMarginAfter and marginLevelAfter are net of estimatedCosts (commission plus slippage allowance). On spot orders reservation says what the order would reserve (a BUY: USDC quote cash including the commission; a SELL: the base asset); null elsewhere.
Request body
Name
Type
Required
Description
symbol
string
yes
The market symbol.
side
string
yes
BUY or SELL.
type
string
yes
The order type, as on POST /v1/orders.
tif
string
no
GTC (default), IOC, FOK or POST_ONLY.
qty
string
yes
Quantity as a decimal string.
price
string
no
LIMIT only, as on POST /v1/orders.
trigger
object
no
Conditional types only, as on POST /v1/orders.
reduceOnly
boolean
no
Preview a reduce-only order.
leverage
integer
no
Preview at this leverage instead of the stored choice; not persisted.
Validation (the sentence in msg; the body also carries accepted: false and reason).
4004
404
"This market is not offered."
5030
503
The margin service is unavailable.
4010
401
No or unknown client identity.
4290
429
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
One row per symbol the client holds a position in, attributing funding to the open lots, the accrual since the last settlement and the closed lots, plus the broker-side charged total (the ledger truth, present even when the venue lookup failed). Answers are cached for 15 seconds per client. The body is BARE (no envelope).
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
Every funding charge and credit booked to the client, newest first, keyset paged. amount is signed at 6 decimals: negative means the client paid. The body is BARE (no envelope).
A limit outside 1..500 or a cursor that is not valid.
4010
401
No or unknown credentials.
4290
429
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
Barrier (knock-out) contracts trade by request for quote, never by order: the client posts a request, anonymous quotes arrive, and an accept opens the contract at the quoted entry. A BUY (long) contract knocks out at a barrier below the entry, a SELL (short) above it; the stop is guaranteed AT the barrier and the holder can never be liquidated on the contract. The premium (the maximum loss) is a frozen share of the barrier distance: a normal contract trades at 10000 minus the market's knockOutRebateBps (the rebate makes it cheaper than the full distance), and a strictTakeProfit contract trades at the market's strict share, way cheaper still, but pays only if the take profit is touched before expiry; an expiry without a touch loses the whole premium whatever the mark. Strict take profit needs a take profit price, an explicit expiry at most 8 hours ahead and a market that offers it. The share is frozen on the contract at accept (premiumBps on every view); a later market change re-prices new contracts only. The barrier must sit at least 0.1 percent away from the reference (1000x maximum leverage). Requests default to autoAccept true: the router accepts the first acceptable live quote itself. An open contract can be closed early by RFQ: the close request goes only to the contract's writer and settles AT the quoted price.
POST/v1/rfqKey: TRADE
Create a barrier RFQ
Posts a request for quotes on a barrier market. Gates: an offered barrier market, an active account, the TRADE:BARRIER verification level and the pre-trade check at the current mark (the premium plus estimated commission must fit the free margin). A request without barrier.expiryTs gets the 12-month default (expiryDefaulted true on the view). autoAccept defaults to true: the router accepts the first acceptable live quote itself. On a 502 the request may still exist at the venue: the row stays OPEN and the poll resolves it either way.
Request body
Name
Type
Required
Description
symbol
string
yes
A barrier market, e.g. BTC-USD-KO.
side
string
yes
BUY (long, knock-out below the entry) or SELL (short, knock-out above); LONG / SHORT accepted.
qty
string
yes
Quantity as a decimal string, on the market's lot grid (lotSize on GET /v1/market-info, the perpetual's step of the same underlying, so a fraction of a unit of the underlying such as 0.0001 BTC on BTC-USD-KO), worth at least the market's minNotional at the current mark: 100 USD on every knock-out market. A request below it is refused before anything is written, with the figure.
barrier
object
yes
{level (required, on the tick grid, at least 0.1 percent from the current price), expiryTs? (Unix ms; omitted = 12 calendar months, never rolled over), takeProfit? (a price on the profit side of the entry; omit for a knock-out-only contract), strictTakeProfit? (default false; needs takeProfit, an explicit expiryTs at most 8 h ahead and a market that offers it)}.
expiresInMs
integer
no
The quoting window in ms (default 60000, 1 s to 24 h).
autoAccept
boolean
no
Default true: the router accepts the first acceptable live quote itself; false keeps the manual accept flow.
patternSetupId
integer
no
The pattern setup id the ticket was pre-filled from; stored as the acted-upon record, never affects pricing or routing.
sharedSetupId
integer
no
The community shared setup id the ticket was pre-filled from; stored as the acted-upon record like patternSetupId (both may ride together), never affects pricing or routing; an unknown id is dropped silently.
Validation, the barrier floor ("The barrier must be at least 0.1 percent away from the current price (1000x maximum)."), the lot grid and the minimum quantity, the minimum request value at the current mark ("The request value is below the minimum for this market (100.00 USD).", refused before anything is written: no request is recorded and the venue is never called), the quoting window, the account state, or the free-margin check. The venue's own minimum applies to every quote at its price too, so a request sized exactly at the minimum may still be refused on a quote below the mark: size with headroom.
4030
403
The verification gate (the missing blocks in data).
4035
403
The CONSENT_REQUIRED family, told apart by data.error: KID_REQUIRED (a RETAIL client's opening request without an acknowledgement of the market's current key information document; data {error, doc, currentVersion, symbol, strict}; msg "Please read and acknowledge the key information document for this market (KID_BTC_USD_KO, version 1) before your first request.", or "The key information document for this market is not available yet, so this contract cannot be requested by retail clients." with currentVersion null while none is published; fetch it from the kid or kidStrict block of GET /v1/market-info through GET /v1/consent-documents/{doc}/{version} and acknowledge it with POST /v1/consents/accept), RISK_DISCLOSURE_CONSENT_REQUIRED (a strict take profit request while the current Risk Disclosure version is not accepted; data {error, doc, currentVersion}; msg "Accept the current Risk Disclosure before requesting a strict take profit contract."), or the TERMS re-consent gate (data {doc, currentVersion, gateFrom}).
4004
404
"This market is not offered."
5020
502
The venue could not be reached (data.rfqId names the row; it stays OPEN and the poll resolves it).
4290
429
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
The ticket's summary without a request: the premium and estimated commission at the current mark, whether the free margin covers them, the implied leverage and the potential profit. The create's gates are mirrored in two ways: a market not offered or disabled, the request shape and the tick grid answer 400 or 404 with the create's sentence, the verification gate 403 code 4030, and a strict take profit preview without the current Risk Disclosure acceptance 403 code 4035 (RISK_DISCLOSURE_CONSENT_REQUIRED); the product gate, a restriction, the TERMS re-consent gate, an account that is not ACTIVE, the contract expiry and the quoting window land as accepted: false with the create's sentence in reason, so a refused preview is not always a margin verdict. One gate is not mirrored: the key information document gate applies to the create only, so a RETAIL client's preview can read accepted: true while POST /v1/rfq still answers 403 code 4035 KID_REQUIRED until the market's current document is acknowledged; read the acknowledged flag of the kid or kidStrict block on GET /v1/market-info before treating a preview as green. The answer also carries the market's pricing facts (knockOutRebateBps, strictTakeProfitPremiumBps, strictTakeProfitOffered) so a ticket can price its own edits.
Request body
Name
Type
Required
Description
symbol
string
yes
A barrier market.
side
string
yes
BUY or SELL (LONG / SHORT accepted).
qty
string
yes
Quantity as a decimal string.
barrier
object
yes
As on POST /v1/rfq.
expiresInMs
integer
no
The quoting window; validated, answered as accepted: false when out of bounds.
Validation (the create's sentences: side, level, take profit, strict shape, the tick and lot grid, the minimum quantity). The minimum request value at the current mark is a REFUSED preview, never a 400: accepted false with the create's sentence in reason ("The request value is below the minimum for this market (100.00 USD)."), with precedence over the margin verdict.
4030
403
The verification gate.
4035
403
A strict take profit preview while the current Risk Disclosure version is not accepted (data.error RISK_DISCLOSURE_CONSENT_REQUIRED, data.doc, data.currentVersion; the same refusal as the create, never a refused preview). The key information document gate applies to the create only: the preview never answers KID_REQUIRED.
4004
404
"This market is not offered."
4290
429
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
The client's OPEN requests plus the ones that ended in the last few minutes (so the ticket can show why the quotes vanished), newest first, each with its mirrored quotes: quoteId, price, qty, validUntil, validForMs, the implied figures each entry would carry (impliedMaxLoss / impliedPremium, impliedLeverage, impliedPotentialProfit; on a close request impliedPnl instead), state (LIVE, EXPIRED, WITHDRAWN, ACCEPTED) and acceptable / notAcceptableReason (whether the router would let the client accept it; the free-margin check is not anticipated here).
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
One request in the same shape as the rows of GET /v1/rfq/mine. On a close request: side / direction are the CLOSING side, closeContractId names the contract, contractEntry its entry price, and each quote carries impliedPnl (the holder-signed realized P&L a close at that price would pay) while the open-side implied figures are null.
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
Accepts one live quote. The wire's acceptability verdict binds the manual accept identically: the same checks the view prints as acceptable run here at a fresh mark (the off-market quote guard at the market's band, the mark through the barrier, the 1000x cap and the market's barrier floor at the quoted entry, the take-profit side and floor), and a market without a fresh mark refuses outright. The pre-trade check then runs again at the QUOTED entry under the client's pipeline lock. On success the contract opens from the venue's answer with the entry commission posted; when the trading venue refuses the accept under its own quote guard the answer is 400 with a sentence naming the term and data.venueCode, and the request stays open for fresh quotes. On a close request the same route hands over to the close settlement: the contract settles AT the quoted price with state EARLY_CLOSE and closeReason earlyClose, and the answer carries settlePrice and realizedPnl instead of the premium figures.
The request is not open, an accept is already in flight, the quote is not acceptable (the guard sentences), or the venue refused (data carries venueCode).
4035
403
The TERMS re-consent gate (data: {doc, currentVersion, gateFrom}) on the accept of an OPENING request; the accept of a close request is never gated.
4004
404
An unknown request, or the quote is gone or expired.
5020
502
The venue could not be reached; the accept stays marked in flight (data: {rfqId, quoteId, accepting: true}) and the poll resolves it.
4290
429
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
Withdraws the request at the venue. A request that never reached the venue is cancelled locally; one the venue already closed or expired follows the poll's verdict (expired by time, else withdrawn). A request with an accept in flight cannot be cancelled until the accept resolves.
The request is not open, an accept is in flight, or the venue refused the cancel (data carries venueCode).
4004
404
Not the client's request.
5020
502
The venue could not be reached.
4290
429
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
curl
curl -X POST https://barriers.dev.mtf.perpetuals.com:9800/v1/rfq/311/cancel -H "X-Api-Key: bak_EXAMPLE"
GET/v1/barriersKey: READ
Barrier contracts (open or history)
state=open (default) answers every OPEN contract valued live: mark and freshness, distance to the barrier and to the take profit, unrealized P&L, the remaining requirement, maxLoss = premium, potential profit, leverage, the expiry countdown and closeRfqId (the client's OPEN close request for it, null when none is pending). state=history answers the terminal contracts newest first, cursor paged by contract id, with settlePrice, pnl and closeReason (knockOut, takeProfit, expiry, liquidation, finalSettlement, earlyClose, reconciler:ledger). Contract states: OPEN, KNOCKED_OUT, TAKE_PROFIT, EXPIRED, CLOSED, TORN_UP, EARLY_CLOSE.
Path and query parameters
Name
In
Type
Required
Description
state
query
string
no
open (default) or history.
limit
query
integer
no
History paging: 1 to 500, default 100.
before
query
integer
no
History paging: the nextBefore of the previous page (a contract id).
An unknown state filter or a malformed limit / before value.
4010
401
No or unknown client identity.
4290
429
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
A request to close one of the client's OPEN contracts early. The request sits on the board only for the contract's writer; the price must lie strictly inside barrier and take profit, and the accept settles the contract AT that price (state EARLY_CLOSE). The verification gate is NOT applied (closing is always allowed) and there is no margin check (closing releases margin); a market the operator disabled still closes. At most one OPEN close request per contract. An empty body is fine; autoAccept defaults to true. The answer is the request view with closeContractId and contractEntry set; a knock-out, take profit, expiry or tear-up while the close request is open cancels it.
Path and query parameters
Name
In
Type
Required
Description
contractId
path
integer
yes
The client's OPEN contract.
Request body
Name
Type
Required
Description
autoAccept
boolean
no
Accept the first acceptable quote of the close request automatically (default true).
expiresInMs
integer
no
The quoting window in ms (default 60000, 1 s to 24 h).
The contract does not exist or is not the client's.
4000
400
The contract is not open, a close request for it is already open (data.rfqId names it), the quoting window is out of bounds, or the account is not active.
5020
502
The venue could not be reached (the row stays OPEN, the poll resolves it).
4290
429
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
Writer-first staking and yield swaps in ONE asset per market (T-Bill yield swaps in USD and EUR, staking swaps in BTC, ETH, SOL and USD1). A writer's standing offer names a rate (rateBps) and a tenor in days; a take books the principal to the writer at once and pays principal plus the frozen reward back at maturity. The reward is floor(notional x rateBps x tenorDays / 3,650,000), frozen at the take. The writer's obligation is unsecured: a short writer is settled partially and the rest stays as the taker's claim (state DEFAULTED) until later sweeps cure it. The broker's commission is a share of the reward, frozen per swap at the take (commissionRewardBps on every view). No early close; swaps ride to maturity.
GET/v1/staking/marketsKey: READ
The staking markets and their terms
The offered STAKING markets with the venue's swap facts: tenor bounds, the maximum rate, size rules and the broker's effective venue taker rate on the market (takerFeeRate, the rate a take reserves for), plus the client's own commission share (the figure a take freezes), the terms writers usually offer (usualTenors, whole days ascending, [] when the market lists none; revision 50) and the disclosure text. The name is the product name (USD Fixed Yield Contract and its siblings since revision 50). A market whose facts the venue did not answer carries zeros / nulls; the accept path fails closed there.
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
The anonymous writer offers, cached a few seconds and served from the router's board; stale is true when the last venue refresh failed and the previous board is served. rewardPerUnit is the reward one unit would earn at the offer's terms, before the broker's commission. Filters: asset (the client currency) and symbol, both optional.
Path and query parameters
Name
In
Type
Required
Description
asset
query
string
no
Only offers in this asset (client currency or venue asset code).
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
Takes a size from a writer's offer. Gates in order: the body, the TRADE:STAKING verification level and an ACTIVE account, the offer on the board, the market offered, the venue facts, the size on the venue's grid and within the offer's rules (at least max(minQty, minNotional) unless it is the whole remainder, at most the remainder and the per-take cap); then, under the client's pipeline lock, no close-out in progress and the free balance in the ASSET must cover the notional plus the venue's taker fee (USDC: min(free cash, free margin)). The commission share is frozen on the row at the take. On a 502 the row stays ACCEPTING with its reservation and the poll adopts the contract or releases it after the grace.
Path and query parameters
Name
In
Type
Required
Description
offerId
path
integer
yes
The offer on the board.
Request body
Name
Type
Required
Description
qty
string
yes
The size to take, in the market's asset, on the venue's lot grid.
The size rules, the venue facts unavailable, a disabled market, the account state, or the balance short of notional plus fee.
4030
403
The verification gate (the missing blocks in data).
4035
403
The TERMS re-consent gate (data: {doc, currentVersion, gateFrom}): a take while the acceptance of the current Terms version is pending past the gate date.
4004
404
The market is not offered or the offer is no longer on the board.
5020
502
The venue could not be reached; the row stays ACCEPTING and the poll resolves it.
4290
429
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
The client's swaps, newest first. state=live (default) answers ACCEPTING, OPEN and DEFAULTED rows; history everything else; all both. expectedPayout is notional plus the frozen reward; claim is the outstanding amount still expected from a defaulted writer, net of write-offs and recoveries.
An unknown state filter or a malformed limit / before value.
4010
401
No or unknown client identity.
4290
429
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
One swap of the client in the same shape as the list rows, looked up by the venue contract id; the router's swapId is accepted too when no contract of THIS client carries that id (the id a 502 answer told the client to watch).
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
The scanner detects chart patterns on the venue's candles (1d, 1h, 15m and 5m boundaries) and derives ready-made barrier setups from them: a plays-out variant and a fade variant, each with a quote priced at the current mark and admitted only when a contract can be made from it right now (including the profit-to-premium ratio floor). The list serves only quotable setups; the detail by id still answers for a dropped row and shows the refusal reasons. Every answer carries the disclosure sentence and the producer block: the setups are investment recommendations of PM MTF Ltd, marketing communications, never personal investment advice. The list, the detail, the sentiment vote, the by-symbol track record and the history answer 403 code 4040 for an account the scanner is hidden on (a FAIL appropriateness result while the MiFID level is not granted, or a restriction); the status stays open.
GET/v1/patternsKey: READ
Quotable pattern setups
The active setups a barrier contract can be made from RIGHT NOW: at least one variant valid with a valid quote at the current mark. Rows without any mark, on disabled markets, on toggled-off timeframes or patterns, or where every variant is refused, are dropped from the list (their detail by id still answers). A disabled scanner answers an empty list with scannerEnabled false. Each setup carries the detection facts (pattern, direction, state, confidence, entry / target / stop) and the derived barrierSetup / fadeSetup with their live quote and fadeQuote priced at the SAME mark.
Path and query parameters
Name
In
Type
Required
Description
assetClass
query
string
no
Filter: the instrument's asset class (e.g. CRYPTO, INDEX, FX).
timeframe
query
string
no
Filter: 1d, 1h, 15m or 5m.
direction
query
string
no
Filter: LONG or SHORT.
state
query
string
no
Filter: the setup state (e.g. FORMED, BREAKOUT).
symbol
query
string
no
Filter: one market symbol.
limit
query
integer
no
1 to 200, default 100.
Response example
{
"code": 0,
"data": {
"disclosure": "...",
"producer": {
"name": "PM MTF Ltd",
"competentAuthority": "Cyprus Securities and Exchange Commission (CySEC)",
"productionTime": "detectedAt (first production) and updatedAt (last update) of each setup",
"priceTime": "candleTs (the last closed candle the setup was read from) and quote.markTs / fadeQuote.markTs (the mark the figures are shown at)",
"updateFrequency": "once per timeframe after every candle close of that timeframe; each setup names its timeframe, and the timeframes in force are the enabledTimeframes member of the status answer",
"horizon": "targetCandles and targetHours of each setup (the target period)",
"disclosureVersion": 2
},
"asOf": 1755855750000,
"count": 1,
"setups": [
{
"id": 5120,
"symbol": "BTC-USD-KO",
"underlying": "BTC-USD-PERP",
"displayName": "Bitcoin",
"assetClass": "CRYPTO",
"timeframe": "1h",
"intervalMs": 3600000,
"pattern": "ASC_TRIANGLE",
"patternName": "Ascending triangle",
"family": "CONTINUATION",
"direction": "LONG",
"state": "BREAKOUT",
"status": "ACTIVE",
"outcome": null,
"detectedAt": 1755850000000,
"updatedAt": 1755855600000,
"stateChangedAt": 1755854400000,
"candleTs": 1755853200000,
"breakoutTs": 1755854400000,
"closedAt": null,
"entry": "65400.0",
"target": "66800.0",
"stop": "64700.0",
"targetCandles": 12,
"targetHours": 12,
"confidence": "0.74",
"confidenceParts": { "shape": "0.8", "volume": "0.6" },
"description": "Ascending triangle with a confirmed breakout above 65400.",
"barrierSetup": { "side": "BUY", "barrier": "64700.0", "takeProfit": "66800.0", "strictTakeProfit": true },
"fadeSetup": { "side": "SELL", "barrier": "65400.0", "takeProfit": "64000.0", "strictTakeProfit": true },
"quote": { "premium": "1000.000000", "potentialProfit": "2140.000000", "valid": true },
"fadeQuote": { "premium": "1000.000000", "potentialProfit": "2050.000000", "valid": true },
"overlay": null
}
],
"scannerEnabled": true
}
}
Errors
Code
HTTP
When
4000
400
An unknown filter value (the sentence names the field).
4010
401
No or unknown client identity.
4040
403
The pattern scanner is not available on this account (a FAIL appropriateness result while the MiFID level is not granted, or a restriction).
4290
429
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
One setup with the drawing overlay and the candle window around it (from the venue, cached per boundary). Answers for hidden or no-longer-quotable rows too, so a deep link can show the refusal reasons. candlesError carries a sentence when the venue window could not be fetched; the setup itself still answers.
Path and query parameters
Name
In
Type
Required
Description
id
path
integer
yes
The setup id.
candles
query
integer
no
The lookback window size (default per timeframe, clamped between 30 and the venue's candle window maximum).
Response example
{
"code": 0,
"data": {
"disclosure": "...",
"producer": {
"name": "PM MTF Ltd",
"competentAuthority": "Cyprus Securities and Exchange Commission (CySEC)",
"productionTime": "detectedAt (first production) and updatedAt (last update) of each setup",
"priceTime": "candleTs (the last closed candle the setup was read from) and quote.markTs / fadeQuote.markTs (the mark the figures are shown at)",
"updateFrequency": "once per timeframe after every candle close of that timeframe; each setup names its timeframe, and the timeframes in force are the enabledTimeframes member of the status answer",
"horizon": "targetCandles and targetHours of each setup (the target period)",
"disclosureVersion": 2
},
"asOf": 1755855750000,
"setup": {
"id": 5120,
"symbol": "BTC-USD-KO",
"underlying": "BTC-USD-PERP",
"timeframe": "1h",
"pattern": "ASC_TRIANGLE",
"direction": "LONG",
"state": "BREAKOUT",
"status": "ACTIVE",
"barrierSetup": { "side": "BUY", "barrier": "64700.0", "takeProfit": "66800.0", "strictTakeProfit": true },
"quote": { "premium": "1000.000000", "potentialProfit": "2140.000000", "valid": true },
"overlay": { "lines": [], "labels": [] }
},
"candles": [
{ "t": 1755849600000, "open": "65210.0", "high": "65490.0", "low": "65105.0", "close": "65400.0", "volume": "312.5" }
],
"candlesError": null,
"candlesAsOf": 1755855750000
}
}
Errors
Code
HTTP
When
4004
404
An unknown setup id.
4010
401
No or unknown client identity.
4040
403
The pattern scanner is not available on this account (a FAIL appropriateness result while the MiFID level is not granted, or a restriction).
4290
429
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
Per configured timeframe the last run (identity, times and status; the run's counters and error text are CRM-only), the next due pass (null for a toggled-off timeframe), the active count, the effective enabledTimeframes and disabledPatterns, fadeStates (the setup states on which the fade variant is offered; a setup of another state answers fadeSetup and fadeQuote null on the list and the detail), the totals, the pattern catalogue, the disclosure sentence, the producer block and houseWriterStatement (the operator's conflicts-of-interest sentence on whether a market maker of PM MTF Ltd writes barrier contracts on the venue; the empty string on an environment where it is not stated). This route stays open on an account the scanner is hidden on.
Response example
{
"code": 0,
"data": {
"enabled": true,
"minConfidence": "0.60",
"maxPerSymbol": 3,
"enabledTimeframes": ["1d", "1h", "15m", "5m"],
"disabledPatterns": [],
"fadeStates": ["EMERGING", "BREAKOUT"],
"timeframes": [
{
"timeframe": "1h",
"intervalMs": 3600000,
"lookback": 300,
"lastRun": {
"runId": 9314,
"boundaryTs": 1755853200000,
"trigger": "SCHEDULE",
"startedAt": 1755853205000,
"finishedAt": 1755853212000,
"status": "OK"
},
"nextDueAt": 1755856800000,
"active": 7
}
],
"active": 23,
"breakouts": 4,
"asOf": 1755855750000,
"catalogue": [
{ "code": "ASC_TRIANGLE", "name": "Ascending triangle", "family": "CONTINUATION" }
],
"disclosure": "...",
"producer": {
"name": "PM MTF Ltd",
"competentAuthority": "Cyprus Securities and Exchange Commission (CySEC)",
"productionTime": "detectedAt (first production) and updatedAt (last update) of each setup",
"priceTime": "candleTs (the last closed candle the setup was read from) and quote.markTs / fadeQuote.markTs (the mark the figures are shown at)",
"updateFrequency": "once per timeframe after every candle close of that timeframe; each setup names its timeframe, and the timeframes in force are the enabledTimeframes member of the status answer",
"horizon": "targetCandles and targetHours of each setup (the target period)",
"disclosureVersion": 2
},
"houseWriterStatement": "...",
"candleSource": "TRADE",
"candleSourceSince": null
}
}
Errors
Code
HTTP
When
4010
401
No or unknown client identity.
4290
429
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
Records the client's answer to the card's question: will this pattern play out (confirm) or fail (fade)? One vote per client per setup, upserted (a later vote replaces the earlier one), accepted only while the setup is ACTIVE. The answer is the updated tally; every pattern view carries the same tally as sentiment with myVote. The portal reveals the split only AFTER the client's own pick, never before.
An unknown variant, or the setup is no longer ACTIVE.
4004
404
An unknown setup id.
4010
401
No or unknown client identity.
4040
403
The pattern scanner is not available on this account (a FAIL appropriateness result while the MiFID level is not granted, or a restriction).
GET/v1/patterns/stats/symbolsKey: READ
Track record by symbol
The pattern track record broken down per symbol: how every (pattern, timeframe, symbol) triple ENDED inside the trailing window (played out = target reached, failed = stop crossed or structure lost, expired = ran out of time; superseded rows count nowhere). rate is played out over decided, null under five decided setups. The portal shows this panel once three badges are earned (GET /v1/achievements unlocks); the route itself answers whoever asks, the gate is presentation.
The pattern scanner is not available on this account (a FAIL appropriateness result while the MiFID level is not granted, or a restriction).
4290
429
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
The record of past pattern setups over a fixed 365-day window by detectedAt, every status included (an ACTIVE setup is a recommendation of the period too), newest first by id. total, proportion (bullish and bearish counts) and byPattern (per pattern and timeframe: total, active, playedOut = target reached, failed = stop crossed or structure lost, expired, superseded counted apart; counts only, never a rate) are over the WHOLE window; the filters symbol (the barrier market or its underlying), timeframe, pattern and direction and the page (limit 1 to 500, default 100; before = the last answer's nextBefore, 0 or absent = from the newest) apply to setups only. An unknown pattern code or symbol answers an empty page. A row carries the setup's identity, life cycle, figures and both stored contract variants (fadeSetup null on a setup stored before the fade variant); no quote, track record, sentiment or overlay. The read is not recorded as a view. Every answer carries the disclosure and the producer block.
Response example
{
"code": 0,
"data": {
"disclosure": "...",
"producer": {
"name": "PM MTF Ltd",
"competentAuthority": "Cyprus Securities and Exchange Commission (CySEC)",
"productionTime": "detectedAt (first production) and updatedAt (last update) of each setup",
"priceTime": "candleTs (the last closed candle the setup was read from) and quote.markTs / fadeQuote.markTs (the mark the figures are shown at)",
"updateFrequency": "once per timeframe after every candle close of that timeframe; each setup names its timeframe, and the timeframes in force are the enabledTimeframes member of the status answer",
"horizon": "targetCandles and targetHours of each setup (the target period)",
"disclosureVersion": 2
},
"asOf": 1757500000000,
"windowDays": 365,
"since": 1725964000000,
"total": 412,
"proportion": { "bullish": 230, "bearish": 182 },
"byPattern": [
{ "pattern": "DOUBLE_TOP", "patternName": "Double top", "timeframe": "1h", "total": 48, "active": 3, "playedOut": 17, "failed": 20, "expired": 5, "superseded": 3 }
],
"count": 1,
"setups": [
{
"id": 5120, "symbol": "BTC-USD-KO", "underlying": "BTC-USD-PERP", "displayName": "Bitcoin", "assetClass": "CRYPTO",
"timeframe": "1h", "intervalMs": 3600000, "pattern": "DOUBLE_TOP", "patternName": "Double top", "family": "REVERSAL",
"direction": "BEARISH", "state": "BREAKOUT", "status": "COMPLETED", "outcome": "TARGET_REACHED",
"detectedAt": 1757400000000, "updatedAt": 1757410800000, "candleTs": 1757407200000, "breakoutTs": 1757403600000, "closedAt": 1757430000000,
"entry": "61250", "target": "60100", "stop": "61900", "targetCandles": 12, "targetHours": "12", "confidence": "0.71",
"barrierSetup": { "...": "the confirm variant, the shape of GET /v1/patterns" },
"fadeSetup": { "...": "the fade variant, or null" }
}
],
"hasMore": false,
"nextBefore": 5120
}
}
Errors
Code
HTTP
When
4000
400
timeframe is not one of 1d, 1h, 15m or 5m, or direction is not BULLISH or BEARISH.
4010
401
No or unknown client identity.
4040
403
The pattern scanner is not available on this account (a FAIL appropriateness result while the MiFID level is not granted, or a restriction).
4290
429
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
A VIRTUAL barrier desk: demo contracts are filled instantly at the current mark, priced and settled by the exact arithmetic of the live product, and paid from a demo wallet of virtual USDC. No ledger money ever moves, nothing reaches the venue, and no verification level is required (any signed-in client may practise after confirming once that they are 18 or older; a client restricted from barrier trading, and everyone during a barrier halt, cannot open new demo contracts). Two gates run on a demo open, in this order: the same restriction and halt gate as a live request (a frozen account, a no-new-orders or close-only restriction on barrier contracts or on every family, a standing halt of barrier contracts or of everything refuses with the live sentence), then a one-time confirmation that the client is 18 or older (POST /v1/demo/confirm-age, kept as a dated self-declaration on the wallet). The demo close, reset and alias are not gated. The weekly league ranks demo results by alias only. Virtual funds, no prizes. The operator may switch the demo off without a deploy: every route of this group then answers 503 code 5030 and GET /v1/app-info publishes features.demo false.
GET/v1/demoKey: READ
Demo wallet, open contracts and history
The demo wallet (balance, starting balance, alias, resets, ageConfirmedAt: the Unix ms of the one-time 18-or-older confirmation, null until it was given), every OPEN demo contract with live figures while the mark is fresh (mark, distancePct, distanceToTpPct, unrealizedPnl), and the newest 50 settled contracts.
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
POST/v1/demo/openSession only
Open a demo contract
An instant fill AT the current fresh mark (no quotes, no writer). Gate order: first the restriction and halt gate of the live barrier request (a frozen account, a no-new-orders or close-only restriction on barrier contracts or on every family, a standing halt of barrier contracts or of everything refuses with the live sentence before anything is read or deducted), then the one-time 18-or-older confirmation (an open before POST /v1/demo/confirm-age is refused with nothing deducted), then the validations exactly like the live ticket: the barrier on the loss side and the take profit (required) on the profit side of the entry, both at least 0.1 percent of the entry away, the lot and minimum quantity grid, strict take profit needs an explicit expiry, an explicit expiry sits at most 8 hours ahead (omit it for open ended), disabled markets refuse. The premium is the entry-to-barrier distance times quantity at the market's premium share and is deducted from the demo wallet; at most 20 demo contracts may be open. The demo close, reset and alias are not gated.
Request body
Name
Type
Required
Description
symbol
string
yes
A barrier (KO) market.
side
string
yes
BUY or LONG (knock-out below), SELL or SHORT (knock-out above).
qty
string
yes
Quantity as a decimal string, on the market's lot grid, a fraction of a unit of the underlying, worth at least the market's minNotional at the current mark.
barrier
string
yes
The knock-out level, loss side of the entry.
takeProfit
string
yes
The take profit, profit side of the entry.
strict
boolean
no
Strict take profit (pays only on a touch); needs expiryTs.
expiryTs
integer
no
Unix ms, at most 8 hours ahead; omit for open ended.
The restriction and halt gate, with the live sentence: "Your account is currently frozen. Please contact support.", "Trading on this market is currently disabled on your account. Please contact support.", "Your account is in close-only mode on this market: you can reduce or close positions but not open new ones. Please contact support.", "Trading in barrier contracts is temporarily halted. Existing positions can still be reduced or closed. Please try again later." or, under a halt of every family, "Trading is temporarily halted. Existing positions can still be reduced or closed. Please try again later."
4000
400
The one-time age confirmation was not given yet: "Confirm that you are 18 or older before your first demo contract." (nothing deducted; POST /v1/demo/confirm-age first).
4000
400
A validation refusal: side, levels, floors, grid, the minimum quantity, the minimum request value at the entry ("The request value is below the minimum for this market (100.00 USD).", the live ticket's floor, so paper trading sizes like real trading), expiry, balance, the open-contract cap, a stale mark, or a disabled market.
4004
404
Not an offered market.
4010
401
No or unknown client identity.
5020
502
The venue's instrument specs were unreachable.
5030
503
The demo account is switched off on this server
POST/v1/demo/confirm-ageSession only
Confirm once that you are 18 or older
The one-time self-declaration the demo desk asks before the first demo contract: send confirmed true and the wallet records the time (ageConfirmedAt, Unix ms). The first timestamp is kept, a repeat changes nothing, nothing is deducted, and the wallet is created on first use like GET /v1/demo. A declaration, not a verification block: it never asks for a document and never changes the account's verification level. Clients who used the demo before this route existed read ageConfirmedAt null and confirm once at their next open.
The body is missing or confirmed is not true: "Tick the box to confirm that you are 18 or older."
4010
401
No or unknown client identity.
5030
503
The demo account is switched off on this server
POST/v1/demo/closeSession only
Close a demo contract now
Settles an own OPEN demo contract at the current fresh mark clamped to the contract's terms (closeReason earlyClose). The wallet is credited the premium plus the realized figure, so the net movement is the realized profit or loss.
Restores the starting balance and counts the reset. Refused while any demo contract is open: close them first. The answer carries the wallet with its ageConfirmedAt.
The name the league shows for this wallet: 3 to 16 letters, digits, spaces, dashes or underscores. The league never shows anything else about a client. The answer carries the wallet with its ageConfirmedAt.
The ranking over the demo contracts SETTLED in one ISO week (UTC): score = total pnl over total premium, eligible from 3 settled contracts. The top 20 rows, plus the caller's own row appended when ranked below; aliases only, never a client id, email or name.
Path and query parameters
Name
In
Type
Required
Description
week
query
string
no
An ISO week like "2026-W35"; omitted = the current week.
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
Clients may share a barrier setup's TERMS (side, barrier, take profit, strict flag, expiration key, optionally a size and a short note) under an alias. A shared setup is that client's own view, never a recommendation: the list and the public share answer carry a disclosure sentence, and the served rows are only those that are live right now (not removed, not ended, on an enabled market, with a fresh mark, and quotable at it: the barrier on the loss side of the mark, the take profit on the profit side, both at least 0.1 percent away). Since revision 72 a shared setup ENDS once 30 minutes or less, the shortest timed expiration key, are left before the share expiry it was stored with (72 hours after the share): from then on it is gone from the list and its public share answers 404, and the served expiresAt is that end (the share expiry less 30 minutes). The public share landing serves the terms and a candle window of the underlying perpetual to anyone with the link (alias only, never a client id or e-mail; per-IP throttled). The referral code is display transparency on share links (the ref query parameter); attribution of a trade to a shared setup keys off the sharedSetupId field of POST /v1/rfq, which the router stores on the request and the contract like patternSetupId (both may ride together). Since revision 48 a client may keep an OPTIONAL profile image, shown beside the alias only where the client allows it per share (the public address is the share token, never a client id), and may OPTIONALLY publish its own barrier results on a share: the client's own settled barrier contracts, frozen on the share at publication, always with the past-performance warning inside the performance member, never ranked. Since revision 49 the performance member on the public share, the profile, the create and the visibility answers also carries series, the graphical results behind the portal's and the app's trader results view (a cumulative curve after commissions, the last twelve months, the outcomes by close reason, at most eight markets, the longest runs of wins and losses; times and aggregates only, never a size, a price or a contract id), right before warning; the board list omits it (read the share for the chart). Prices and money are decimal strings; timestamps Unix milliseconds UTC.
POST/v1/community/setupsSession only
Share a barrier setup
Stores the caller's setup terms for sharing and answers the stored row with its public token. side accepts LONG or BUY (long) and SHORT or SELL (short); expiryKey is one of 30m, 1h, 4h, 8h or none (none = the broker's open-end default at trade time); note is optional, control characters are stripped and at most 140 characters are kept. drawings optionally carries the creator's chart drawings (at most 20); an unknown drawing type, an unknown tv tool name or a malformed point refuses the create with a plain sentence, and the stored drawings are served back verbatim on the list rows and the public share answer. Nothing is traded by sharing; the row appears on the community list while it stays quotable and on its public share page.
Request body
Name
Type
Required
Description
symbol
string
yes
A BARRIER market, e.g. BTC-USD-KO.
side
string
yes
LONG, SHORT, BUY or SELL (stored as LONG or SHORT).
barrier
string
yes
The knock-out level as a decimal string.
takeProfit
string
no
The take profit level; omitted for a knock-out-only setup.
strict
boolean
yes
The strict take profit flag of the setup.
expiryKey
string
yes
30m, 1h, 4h, 8h or none.
qty
string
no
The size the setup was drafted at; omitted when the ticket was sized by premium.
note
string
no
A short note shown on the community card; at most 140 characters, control characters stripped.
drawings
array
no
The creator's chart drawings, at most 20 items, each {type, points} with type one of trendline, hline, vline, box, fib or tv and points an array of {t: Unix ms integer, p: decimal string}. A trendline, a box and a fib carry exactly 2 points (the box as opposite corners, the fib as its two anchors), an hline exactly 1 (its p matters, t is 0), a vline exactly 1 (its t matters, p is "0"). A tv drawing additionally carries name, a whitelisted TradingView tool name (for example ghost_feed, parallel_channel, pitchfork or arrow_up; free-text tools are never accepted), and 1 to 10 points (the trend-based fib extension fib_trend_ext exactly 3). Absent or [] means none; an unknown type, an unknown tool name or a malformed point refuses the create with a plain sentence, nothing is silently changed.
showImage
boolean
no
Show your profile image on this share (revision 48). Absent or null = your settings default, applied only while a live image exists; an explicit true without an image is refused 400 "You have no profile image to show. Add one in your settings first."; a non-boolean value 400 "Send showImage or showPerformance.".
showPerformance
boolean
no
Publish your barrier results on this share (revision 48): the figures are computed once and FROZEN on the row, since revision 49 together with the series of the results view (the answer's performance carries it right before warning; the example's curve is shortened). Absent or null = your settings default, applied only while you are eligible; an explicit true below the minimum sample is refused 400 "Your results can be shared once at least 20 of your barrier contracts settled with a profit or a loss." (the number from the venue's configuration; while the venue's performanceMinAgeDays is above 0 the sentence gains the age clause "and at least N days have passed since your first settled barrier contract", and an explicit true below that age is refused with it).
Validation refused the terms (the sentence in msg); also an explicit showImage true without a profile image, an explicit showPerformance true below the minimum sample, or a non-boolean flag.
The shared setups that are live AND quotable right now, each with a preview priced at the CURRENT fresh mark (premium per unit, potential profit per unit, leverage). Rows that ended, were removed, or stopped being quotable at the mark are not served, so a polled list shrinks between reads; every row carries mine (creator = caller). Since revision 48 every row also carries image ({url, v}, the creator's profile image by SHARE TOKEN with a cache-busting version; null unless the share allows it and the creator has a live image right now) and performance (the results snapshot frozen on the row at publication, the warning sentence inside; null unless the share publishes them; WITHOUT the series of revision 49: the member is omitted here, so a list row is byte-identical to revision 48, and a client that opens the results view reads GET /v1/share/{token} for the chart with the row's token), and the caller's OWN rows carry visibility {showImage, showPerformance}. Order and filter are unchanged; there is no sort or filter parameter and nothing ranks creators. The answer carries the disclosure sentence shown with the list.
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
Removes one of the caller's OWN shared setups: it leaves the community list and its share link answers 404 from then on. Only the creator can remove a setup.
POST/v1/community/setups/{id}/visibilitySession only
Change what one of your shared setups shows
The per-share retraction and refresh on a LIVE setup of your own (revision 48): at least one of showImage and showPerformance, booleans. showImage true needs a live profile image (400 "You have no profile image to show. Add one in your settings first."); showPerformance true takes a FRESH snapshot of your results now (400 "Your results can be shared once at least 20 of your barrier contracts settled with a profit or a loss." below the minimum, with the age clause "and at least N days have passed since your first settled barrier contract" added while the venue's performanceMinAgeDays is above 0; the row's figures change with it, by design); showPerformance false DROPS the stored snapshot (performance reads null on every later read, it is not hidden). A row that is not yours, removed or expired answers 404, never 403. Answers the stored row in the own-row shape (no preview); since revision 49 its performance carries series (a fresh one after showPerformance true; null when only the image flag moved on a row whose snapshot predates the series, in which case turning your results off and on again refreshes it).
Path and query parameters
Name
In
Type
Required
Description
id
path
integer
yes
The shared setup's id.
Request body
Name
Type
Required
Description
showImage
boolean
no
Show your profile image on this share.
showPerformance
boolean
no
Publish (a fresh snapshot) or withdraw (drop the snapshot) your barrier results on this share.
Neither member, a non-boolean value ("Send showImage or showPerformance."), showImage true without an image, or showPerformance true below the minimum sample.
Your alias (the demo wallet's, the one public name), your live profile image (image, null without one; url is the session route below), imageRemovedByOperator ({at} while the last removal was by our team and no newer image exists; the time only), your defaults for new shares, uploadBlocked, your LIVE barrier results (performance, computed now; null below the minimum sample) with decidedContracts, performanceMinContracts, performanceMinAgeDays (0 = no age condition; above 0, your first settled barrier contract must be at least that many days old before your results can be shared, and performanceEligible already reflects it) and performanceWindowDays served either way (0 = your whole history since your first settled contract), the warning sentence and the disclosure. Your results count your own settled real barrier contracts only: wins and losses are the contracts with a positive or negative result, a zero result counts in settled but in neither, the win rate is floored to a whole percent, realized is net of commissions and premiumAtRisk the premium you put at risk over the same contracts. Since revision 49 performance also carries series, the graphical results of your own live figures (never null while performance is non-null): curve, the cumulative realised result after commissions over your settled contracts oldest first as [{t, v}] with at most 120 points (above that the router keeps every 119th share of the points evenly with the first and the last always kept, a kept point carrying its original running total, so the last v equals realized; a single settled contract is a one-point curve); months, the last twelve UTC calendar months ending in the month of asOf, oldest first, months with nothing settled included with zeros; outcomes, the settled contracts by close reason (takeProfit, knockOut, expiry for the venue's expiry and final settlement, earlyClose, other for everything else), summing to settled; symbols, at most eight rows by settled count descending then symbol ascending; bestWinRun and worstLossRun, the longest runs of consecutive wins and losses (a zero result breaks neither and counts in neither). Times and aggregates only: no size, price or contract id. The example's curve is shortened. API keys never reach this route.
Response example
{
"code": 0,
"data": {
"alias": "Luna",
"image": { "url": "/v1/community/profile/image", "v": "3f9a1c0b7e2d", "sizeBytes": 48211, "width": 512, "height": 512, "updatedAt": 1756900000000 },
"imageRemovedByOperator": null,
"defaults": { "showImage": false, "showPerformance": false },
"uploadBlocked": false,
"performance": {
"windowDays": 0, "from": 1756200000000, "to": 1756900000000, "asOf": 1756900000000,
"settled": 47, "wins": 29, "losses": 16, "winRatePct": 64,
"realized": "812.400000", "premiumAtRisk": "9150.000000", "currency": "USDC", "minContracts": 20,
"series": {
"curve": [
{ "t": 1756200000000, "v": "-12.500000" },
{ "t": 1756286400000, "v": "31.900000" },
{ "t": 1756600000000, "v": "402.150000" },
{ "t": 1756900000000, "v": "812.400000" }
],
"months": [
{ "m": "2024-10", "settled": 0, "wins": 0, "losses": 0, "realized": "0.000000" },
{ "m": "2024-11", "settled": 0, "wins": 0, "losses": 0, "realized": "0.000000" },
{ "m": "2024-12", "settled": 0, "wins": 0, "losses": 0, "realized": "0.000000" },
{ "m": "2025-01", "settled": 0, "wins": 0, "losses": 0, "realized": "0.000000" },
{ "m": "2025-02", "settled": 0, "wins": 0, "losses": 0, "realized": "0.000000" },
{ "m": "2025-03", "settled": 0, "wins": 0, "losses": 0, "realized": "0.000000" },
{ "m": "2025-04", "settled": 0, "wins": 0, "losses": 0, "realized": "0.000000" },
{ "m": "2025-05", "settled": 0, "wins": 0, "losses": 0, "realized": "0.000000" },
{ "m": "2025-06", "settled": 0, "wins": 0, "losses": 0, "realized": "0.000000" },
{ "m": "2025-07", "settled": 0, "wins": 0, "losses": 0, "realized": "0.000000" },
{ "m": "2025-08", "settled": 31, "wins": 19, "losses": 11, "realized": "502.400000" },
{ "m": "2025-09", "settled": 16, "wins": 10, "losses": 5, "realized": "310.000000" }
],
"outcomes": { "takeProfit": 18, "knockOut": 14, "expiry": 9, "earlyClose": 5, "other": 1 },
"symbols": [
{ "symbol": "BTC-USD-KO", "settled": 21, "wins": 13, "losses": 7, "realized": "410.000000" },
{ "symbol": "ETH-USD-KO", "settled": 12, "wins": 8, "losses": 4, "realized": "201.400000" },
{ "symbol": "SOL-USD-KO", "settled": 8, "wins": 5, "losses": 3, "realized": "120.000000" },
{ "symbol": "XAU-USD-KO", "settled": 6, "wins": 3, "losses": 2, "realized": "81.000000" }
],
"bestWinRun": 6,
"worstLossRun": 3
},
"warning": "These are this client's own past results on barrier contracts, after commissions, over the period shown. Past performance is not a reliable indicator of future results. You can lose the whole premium. Figures are in USDC (treated as US dollars); in your own currency the result may increase or decrease with exchange rates."
},
"performanceEligible": true,
"decidedContracts": 45,
"performanceMinContracts": 20,
"performanceMinAgeDays": 0,
"performanceWindowDays": 0,
"warning": "These are this client's own past results on barrier contracts, after commissions, over the period shown. Past performance is not a reliable indicator of future results. You can lose the whole premium. Figures are in USDC (treated as US dollars); in your own currency the result may increase or decrease with exchange rates.",
"disclosure": "Shared setups are other clients' own configurations, not recommendations or advice. Prices move; check every figure before trading."
}
}
Upload a profile image as multipart/form-data with the field file: a JPEG or PNG up to 8 MB, between 96 and 4096 pixels on each side, a still image. The upload is decoded and re-encoded on the server into a 512 by 512 JPEG (the centre square, upright, no metadata); nothing of the upload itself is stored or served. Checks in order: the throttle of 5 uploads per hour (429 "Too many uploads. Please wait a while before changing your profile image again.") and a moderation block (403 code 4039 "Profile images are not available on this account. Contact support if you have a question.") before the body is read; not multipart 400 "Send the image as multipart/form-data with the field file."; above 8 MB 413 "The file is too large. The maximum size is 8 MB."; no file 400 "A file is required."; empty 400 "The file is empty."; not a JPEG or PNG by its bytes 400 "The image must be a JPEG or PNG."; a side outside 96 to 4096 pixels 400 "The image must be between 96 and 4096 pixels on each side."; more than one frame 400 "The image must be a still image."; unreadable 400 "The image could not be read. Try another file.". Answers 201 with the stored image member; every live share that allows the image shows the new one at once.
Request body
Name
Type
Required
Description
file
file
yes
The image (multipart part named file): JPEG or PNG, up to 8 MB.
Not multipart, no file, an empty file, not a JPEG or PNG, a side outside 96 to 4096 pixels, an animated file, or an unreadable file (the sentence in msg); 413 above 8 MB.
4010
401
No or unknown client identity.
4039
403
Profile image uploads are blocked on this account.
4290
429
More than 5 uploads within an hour.
curl
curl -X POST https://barriers.dev.mtf.perpetuals.com:9800/v1/community/profile/image -H "Authorization: Bearer SESSION" -F "file=@me.jpg"
GET/v1/community/profile/imageSession only
Your profile image
Your own live profile image as image/jpeg (HEAD answers the headers only): ETag is the sha256 of the served bytes, Cache-Control private, no-cache, and a matching If-None-Match answers 304. 404 "You have no profile image." without one. This is a session route: a plain <img> cannot load it; the portal reads it into an object URL.
Removes your profile image. Every live share that showed it stops showing it on its next read (the image is read live; a share stores only your choice). 404 "You have no profile image." without one.
Moves the defaults the share panel pre-sets: only the members you send change. A default is a PREFERENCE: it is accepted without an image and below the minimum sample, and it takes effect on a new share only while it can (an image exists, your results are eligible). Existing shares are not touched. Answers the full profile shape of GET /v1/community/profile.
Request body
Name
Type
Required
Description
showImage
boolean
no
Pre-set "show my profile image" on new shares.
showPerformance
boolean
no
Pre-set "show my barrier results" on new shares.
Response example
{
"code": 0,
"data": {
"alias": "Luna",
"image": null,
"imageRemovedByOperator": null,
"defaults": { "showImage": false, "showPerformance": true },
"uploadBlocked": false,
"performance": null,
"performanceEligible": false,
"decidedContracts": 14,
"performanceMinContracts": 20,
"performanceMinAgeDays": 0,
"performanceWindowDays": 0,
"warning": "These are this client's own past results on barrier contracts, after commissions, over the period shown. Past performance is not a reliable indicator of future results. You can lose the whole premium. Figures are in USDC (treated as US dollars); in your own currency the result may increase or decrease with exchange rates.",
"disclosure": "Shared setups are other clients' own configurations, not recommendations or advice. Prices move; check every figure before trading."
}
}
Errors
Code
HTTP
When
4000
400
Neither member, or a non-boolean value: "Send showImage or showPerformance."
The caller's referral code (8 characters of A to Z and 0 to 9, created on the first read) and its tally: how many times another client traded a setup the caller shared, and the recorded share (a record on the account). Since docs/08 revision 57 the answer also carries referralPayouts (whether this deployment pays referrals: the quarterly payout run is on and the recorded share is above zero, the exact rule behind the referrerInterest sentence of GET /v1/share/{token}) and referralNote, the sharer-side sentence to show the client as served: no payment is made for a referral unless the client is paid, in which case the sentence names the recorded share and the quarterly payout. Since revision 61 it also carries payoutsEnabled (whether this deployment pays it out to YOU: the deployment's rule and the account not serviced by a partner that holds its clients' money; referralNote follows this flag) and sharePct (the share as a percentage string, null while not paid). Show the note as is; never compose a claim about payment from earned. The code rides share links as the ref query parameter for display transparency; attribution keys off sharedSetupId on POST /v1/rfq.
Response example
{
"code": 0,
"data": {
"code": "K7QM2X9A",
"timesTraded": 3,
"earned": "12.500000",
"currency": "USDC",
"referralPayouts": false,
"referralNote": "Share links carry your code so we can see which shared setups are traded; no payment is made for a referral.",
"payoutsEnabled": false,
"sharePct": null
}
}
Errors
Code
HTTP
When
4010
401
No or unknown client identity.
4290
429
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
The public landing data of one shared setup: the terms under the creator's ALIAS (never a client id or e-mail), the creator's chart drawings served back verbatim on the setup row, the current mark, a 15 minute candle window of the UNDERLYING perpetual (about 96 rows, proxied from the venue) and the disclosure sentence. Since revision 48 the setup also carries image ({url, v, onUnfurl}: the creator's profile image by share token where the share allows it, onUnfurl the router's switch for the link preview card, false by decision (the platforms that fetch the card keep their own copy of it)) and performance (the creator's barrier results frozen at publication, the warning inside; null unless the share publishes them; since revision 49 with series right before warning, the graphical results the trader results view draws when you open the creator's avatar or alias: the cumulative curve after commissions of at most 120 points, the last twelve UTC months, the outcomes by close reason, at most eight markets and the longest runs, exactly as GET /v1/community/profile describes them; null on a share published before the series existed, and never a size, a price or a contract id; the example's curve is shortened); never visibility, never mine. No session is required and a sent bearer is ignored; the route is throttled per IP. An unknown, removed or expired token answers 404.
The creator's live profile image by SHARE TOKEN (revision 48; a client id never appears in an image address) as image/jpeg, at most 512 KiB; HEAD answers the headers only. No session is required and a sent bearer is ignored; the route has its own throttle of 300 requests per minute per address (429 "Too many requests. Please wait a minute and try again."). ONE uniform 404 "This shared setup was not found." for an unknown or overlong token, a removed or expired share, a share that does not show the image, a creator without an image, or a closed account (a caller cannot tell which). Otherwise ETag is the sha256 of the bytes, Cache-Control private, max-age=300, and a matching If-None-Match answers 304; a ?v= query is accepted and ignored (the clients append the setup's image.v so a replaced image is re-fetched).
Path and query parameters
Name
In
Type
Required
Description
token
path
string
yes
The share token from the setup's link.
v
query
string
no
A cache buster (the setup row's image.v); ignored by the router.
The signed-in identity, the account overview, the balances with the one portfolio valuation, the stored equity history, and the GDPR surfaces (consents and the closure request). The reads are reachable with an API key of scope READ; the consents and closure routes are excluded for keys whatever the scope (403 code 4034), they are session surfaces only.
GET/v1/meKey: READ
The signed-in client's own identity
The identity for the nav, the settings page and the verification chip (contract A8): the client id and pseudonymous uid, the e-mail with its verification flag, whether two-factor is enabled, the categorization and lifecycle state, the verification level, the residency country and the case counters. The answer is the bare object (the route predates the {code, data} envelope).
No, unknown or expired credential ("Sign in again.")
4290
429
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
The Portfolio page's document (contract F2, docs/08 revision 17): the margin figures, the reserved USDC, the asset holdings with FIFO cost basis, the staked principal, the one portfolio value (equity plus holdings plus staked), lifetime and 30-day realised figures per product family, the win and loss counters, the allocation, the verification level and the case counters. Money fields are 6-place USDC strings. Since docs/08 revision 50 every holdings and staked row carries valuationBasis (CASH the unit of account, MARK at a market mark, PAR at 1 USDC per unit without a market price, NONE no mark right now) and the root the aggregate over the non-cash values (MARK, PAR or MIXED).
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
Balances per currency with the portfolio valuation
One row per currency the client holds, the USDC cash row first (always present, carrying the risk figures equity, unrealizedPnl and freeMargin), then the asset holdings with their indicative USDC value at the spot or perpetual mark. portfolioValue is the ONE portfolio figure shared with GET /v1/account/overview and the equity history's live point: the ledger equity (USDC cash and reserved plus perpetual and barrier unrealised P&L) plus valued holdings plus staked principal; its components ride in portfolio, since docs/08 revision 57 with withdrawalHolds, so that cashTotal - withdrawalHolds + holdingsValue + stakedValue + unrealizedPnl = portfolioValue. A pending USDC withdrawal's reservation (amount plus fee, REQUESTED, APPROVED or SUBMITTING) is outside portfolioValue on both routes and on the equity history, exactly as it is outside equity on GET /v1/account; it returns to the figure when the withdrawal is cancelled or rejected. An asset withdrawal's reservation stays inside the holdings value until it is paid. Since docs/08 revision 50 every row carries valuationBasis (CASH on the USDC row, MARK at a market mark, PAR at 1 USDC per unit without a market price with mark null, NONE no mark right now) and the portfolio block the aggregate over the non-cash values (MARK, PAR or MIXED). The route predates the {code, data} envelope and answers the bare shape.
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
The stored equity points of a range plus the live point
The stored snapshots inside the range, ascending and thinned to at most 400 points, plus a live point computed now. changeAbs and changePct measure the live equity against the FIRST point; portfolioChangeAbs and portfolioChangePct measure the portfolio value against the first point that RECORDED one (points stored before the holdings columns existed carry null there) under the CURRENT valuation basis: valuationVersion on the root is the router's current version (1 today), on every point and on live the version it was computed under, and a point of another version keeps plotting but never anchors the change (docs/08 revision 50). liveMarksFresh is false while a mark has not been seen since a restart; liveHoldingsValued is false when a held or staked asset had no mark.
Path and query parameters
Name
In
Type
Required
Description
range
query
string
no
One of 24h, 7d, 30d, 90d, all; default 7d. An unknown value answers 400 code 4000 with "The range must be one of 24h, 7d, 30d, 90d, all."
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
Lifetime figures over every family: realized P&L and commission for perp and spot fills, barrier contracts, staking swaps and the spot FIFO fold, funding paid, trade counts and the first-trade timestamp. Answers the envelope ({code: 0, data}).
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
The GDPR consent status (docs/08 revision 21): per document the current version, what the client accepted and when, and whether a re-consent is required. Registration terms and declaration blocks count as implied acceptances. Since revision 50 every document carries gateFrom (Unix ms, or null): the instant from which the TERMS re-consent gate refuses NEW positions (an opening order, a barrier request and its opening accept, a staking take; 403 code 4035) until the current version is accepted, null while the document is not gated or the gate is off; reducing orders, early closes, deposits and withdrawals are never gated. The whole /v1/consents namespace is excluded for API keys (403 code 4034), session bearer only. The answer is the bare status document.
Records the acceptance (channel PORTAL, IP and user agent kept as evidence) and answers the same status document as GET /v1/consents (gateFrom included). Excluded for API keys (403 code 4034).
Request body
Name
Type
Required
Description
doc
string
yes
The document code (e.g. TERMS, PRIVACY)
version
string
yes
Must be the CURRENT version of the document; a superseded or not yet effective version is refused
The PDF of a consent document version (the key information documents)
Streams the PDF filed for a version that is in force (revision 50): Content-Type application/pdf, Content-Disposition inline; filename="{doc}-{version}.pdf", ETag "<sha256 of the bytes>" (a matching If-None-Match answers 304), Cache-Control private, no-store, X-Content-Type-Options nosniff, Content-Security-Policy sandbox. Every GET is logged as a delivery to the signed-in client (the instant, the address and the user agent; a 304 too), HEAD answers the headers and logs nothing. One uniform 404 for an unknown document, an unknown or withdrawn version, a version without a file or a version not yet in force. The same document is public without a session at the URL the market-info block names (/public/consent-documents/{doc}/{version}, and /{doc}/current for the newest in force, on the router's public origin; cached five minutes, throttled per address, counted anonymously). Reading is READ for API keys; the acknowledgement (POST /v1/consents/accept) stays a session act.
Path and query parameters
Name
In
Type
Required
Description
doc
path
string
yes
The consent document code, e.g. KID_BTC_USD_KO or KID_BTC_USD_KO_STRICT (the kid and kidStrict blocks of GET /v1/market-info name it); upper-cased
version
path
string
yes
The version tag as the market-info block or GET /v1/consents names it
Response example
<the PDF bytes>
Errors
Code
HTTP
When
4010
401
No session or key
4004
404
"This document was not found." (unknown document, unknown or withdrawn version, no file, not yet in force)
4290
429
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
Whether a closure was requested and whether the account is closed. The whole /v1/account/closure namespace is excluded for API keys (403 code 4034), session bearer only. The answer is the bare object.
Records the request once (a repeat answers "Your closure request is being processed."), opens a case for the closure officer and sends an acknowledgement mail. The closure itself is an officer's decision once every position and contract is settled and the balance returned. Excluded for API keys (403 code 4034).
Request body
Name
Type
Required
Description
reason
string
no
An optional reason, at most 500 characters
Response example
{
"requested": true,
"requestedAt": 1787356800000,
"message": "We received your request. Our team will close the account once every position is settled and the balance returned."
}
Errors
Code
HTTP
When
4010
401
No session
4092
409
The account is already closed ("Your account is already closed.")
The eight-badge SKILL catalogue with the client's earned dates (null = not earned): know the product, first contract, pattern trader, profit locked, strict studies, close call, demo graduate, league contender. Badges are awarded by the router (a sweep over the client's own history) and unlock TOOLS, never fees or trade credits: three badges unlock the by-symbol pattern track record (unlocks.patternSymbolStats, read by the portal's patterns page). A new badge also lands as an ACHIEVEMENT_EARNED notification.
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
The crypto money desk (docs/08 revision 20): the configuration, the custody deposit address, the withdrawal allowlist with its 24 hour cooling period, withdrawal requests with the TOTP step-up, and the movement history. The reads are key-read EXCEPT the deposit address, whose GET provisions an address at custody and is therefore excluded for keys; every mutation is a session surface only. Amounts are decimal strings in the asset (USDC 6 places, BTC 8).
GET/v1/money/configKey: READ
What the money page needs
The offered asset and network pairs with the broker's withdrawal fee per asset and network and minimum per asset, the allowlist cooling hours, the client's daily and single withdrawal limits in USDC value and what is left of the daily one, the self-hosted proof threshold (since docs/08 revision 52 the EUR figure selfHostedProofAboveEur, null = never, the rate eurPerUsdc it is converted at, and the computed USDC figure selfHostedProofAboveUsdc the withdrawal gate applies), and whether the account's two-factor makes the money mutations ask for the authenticator code (mfaStepUp). The network member is the code custody offers for the pair, matched exactly after upper-casing (ETH, SOL, BTC, BSC, XRP on the default matrix); send it as this read lists it, never a network's name.
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
The custody deposit address for an asset and network
The client's deposit address for the pair, PROVISIONED AT CUSTODY on first use and stable afterwards. Because this GET has a side effect at the custodian it is excluded for API keys by the matrix (403 code 4034); it is an ordinary signed-in surface for a session. Requires the MiCA verification level (the DEPOSIT:CRYPTO gate).
Path and query parameters
Name
In
Type
Required
Description
asset
query
string
yes
The asset code (USDC, BTC, ...)
network
query
string
yes
The network code of the pair, as networks[].network of GET /v1/money/config lists it (ETH, not Ethereum)
Every standing allowlist entry with its cooling state: active is false until activatesAt (the cooling period, 24 hours by default) and after a removal. Withdrawals go only to an active entry. Since docs/08 revision 52 a VASP entry carries beneficiaryCountry and every entry carries proof {kind, status, verifiedAt}, the ownership evidence of a self-hosted address (kind NONE, SIGNED_MESSAGE, MICRO_DEPOSIT or ATTESTATION; status NONE, PENDING, VERIFIED or REJECTED; never who verified it).
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
Adds an entry; it becomes usable after the cooling period (activatesAt). On a two-factor account the call is a TOTP step-up like the withdrawal's. A money mutation, excluded for API keys (403 code 4034). Answers the new entry.
Request body
Name
Type
Required
Description
asset
string
yes
The asset code of the pair
network
string
yes
The network code of the pair, as networks[].network of GET /v1/money/config lists it (ETH, not Ethereum)
address
string
yes
8 to 128 characters, no whitespace
memo
string
no
Destination memo or tag where the network uses one
label
string
yes
A short label, 1 to 60 characters
walletType
string
yes
SELF_HOSTED or VASP (an account at an exchange or custodian)
beneficiaryName
string
no
Required for a VASP entry: the account holder at the exchange or custodian
vaspName
string
no
The exchange or custodian's name
vaspLei
string
no
The exchange or custodian's LEI
beneficiaryCountry
string
no
Required for a VASP entry: the ISO 3166-1 alpha-2 country where the exchange or custodian holds the account; ignored on a self-hosted entry
ownershipProof
string
no
An optional note on how the client controls a self-hosted address; since docs/08 revision 52 the withdrawal gate above the threshold reads the VERIFIED evidence added through the challenge and evidence routes, never this note (TFR Article 14(5))
code
string
no
The six-digit authenticator code; required on a two-factor account (the withdrawal-shaped step-up)
POST/v1/money/allowlist/{allowlistId}/challengeSession only
The message to sign for the ownership evidence of a self-hosted address
The ownership evidence of a self-hosted entry (docs/08 revision 52, TFR Article 14(5)): a text to sign with the key of the address, valid 24 hours, a new call replaces it. No body and no authenticator code. Withdrawals above the config's selfHostedProofAboveUsdc to a self-hosted entry need proof.status VERIFIED. Excluded for API keys (403 code 4034).
Path and query parameters
Name
In
Type
Required
Description
allowlistId
path
number
yes
The client's own SELF_HOSTED entry whose evidence is NONE or REJECTED
A VASP entry ("A VASP address needs no ownership evidence."), or an entry already verified ("This address is already verified.")
4000
409
Evidence for this address is already under review
4000
403
A withdrawal restriction stands on the account (FROZEN, NO_WITHDRAWALS)
4038
403
The account is serviced by a partner
5030
503
The money desk is not available on this server
curl
curl -X POST https://barriers.dev.mtf.perpetuals.com:9800/v1/money/allowlist/3/challenge -H "Authorization: Bearer <session-token>"
POST/v1/money/allowlist/{allowlistId}/evidenceSession only
Submit the ownership evidence of a self-hosted address
Records the evidence of the kind: a SIGNED_MESSAGE is verified in-process and the entry is VERIFIED at once; a MICRO_DEPOSIT is VERIFIED at once when custody already reported the transfer with the address among its sending addresses, else PENDING under a compliance review (a later matching report verifies it); an ATTESTATION is PENDING under the same review. A rejected review turns the entry REJECTED and new evidence may be submitted. The cooling period is untouched. Answers the entry with its proof. Excluded for API keys (403 code 4034).
Path and query parameters
Name
In
Type
Required
Description
allowlistId
path
number
yes
The client's own SELF_HOSTED entry whose evidence is NONE or REJECTED
Request body
Name
Type
Required
Description
kind
string
yes
SIGNED_MESSAGE, MICRO_DEPOSIT or ATTESTATION
signature
string
no
SIGNED_MESSAGE: the signature over the current challenge, 1 to 512 characters (EIP-191 hex on ETH and BSC, the BIP-137 base64 signed message on BTC, base58 or base64 Ed25519 on SOL, hex over the SHA-512Half on XRP)
publicKey
string
no
SIGNED_MESSAGE on XRP: the 33-byte public key of the address in hex (required there, ignored elsewhere)
txHash
string
no
MICRO_DEPOSIT: the transaction hash of a small transfer FROM the address to the client's own deposit address, 8 to 128 characters
text
string
no
ATTESTATION: how the client controls the address, 10 to 500 characters
code
string
no
The six-digit authenticator code; required on a two-factor account, evaluated after the checks so a refused submission never consumes a code
A VASP entry, an entry already verified, the kind ("Choose the kind of evidence: SIGNED_MESSAGE, MICRO_DEPOSIT or ATTESTATION."), a missing field ("Enter the signature.", "Enter the transaction hash of the test transfer.", "Describe how you control this address (10 to 500 characters)."), no live challenge ("Request a challenge first; it is valid for 24 hours."), a network without signature verification ("Signed messages are not verified on this network yet; use a small test transfer or a written attestation."), or a mismatch ("The signature does not match this address.")
4000
409
Evidence for this address is already under review
4033
400
A two-factor account sent no code (data.mfaRequired)
4290
429
Five wrong codes inside 15 minutes locked the step-up (data.lockedUntil; data.signedOut at the lock: every session is signed out)
4000
403
A withdrawal restriction stands on the account (FROZEN, NO_WITHDRAWALS)
Creates the movement in state REQUESTED and reserves the amount PLUS the fee out of cash at once (GET /v1/balances shows it under reserved; the reservation is NOT margin equity). The gates in order: pair, amount and minimum, verification (WITHDRAW:CRYPTO), restrictions, the allowlist entry standing and past its cooling, the reconciliation hold, the asset's USDC value, the travel-rule evidence for a self-hosted address above the threshold, the single and daily limits (decided under the client's lock), the free funds, then the TOTP step-up. A money mutation, excluded for API keys (403 code 4034). Answers the movement.
Request body
Name
Type
Required
Description
asset
string
yes
The asset code
network
string
yes
The network code of the pair, as networks[].network of GET /v1/money/config lists it (ETH, not Ethereum)
allowlistId
number
yes
An ACTIVE entry of the client's own allowlist for the same asset and network
amount
string
yes
Decimal string in the asset; at least the per-asset minimum
code
string
no
The six-digit authenticator code; required on a two-factor account
The pair, the amount ("Enter a valid amount."), the minimum, an entry that is not the client's saved address, one still cooling (data.activatesAt), the self-hosted evidence (data.selfHostedProofAboveUsdc), the single or daily limit, or the free funds (data.free, data.need)
4030
403
The WITHDRAW:CRYPTO verification gate
4000
403
A withdrawal restriction (FROZEN, NO_WITHDRAWALS)
4000
409
The reconciliation hold, or "Withdrawals are on hold while your account is in margin call or close-out."
4033
400
A two-factor account sent no code (data.mfaRequired)
4290
429
The step-up wrong-code lock (data.lockedUntil; data.signedOut at the lock: every session is signed out)
5030
503
The asset's value cannot be determined, custody or the margin service is unavailable
POST/v1/money/withdrawals/{movementId}/cancelSession only
Cancel a withdrawal while it is REQUESTED
Cancels the client's own request while it is still REQUESTED; the reservation returns to cash. Once approved or later it can no longer be cancelled. A money mutation, excluded for API keys (403 code 4034). Answers the movement (state CANCELLED).
Unknown or another client's movement ("This movement was not found.")
4000
409
Not REQUESTED any more ("This withdrawal can no longer be cancelled.")
curl
curl -X POST https://barriers.dev.mtf.perpetuals.com:9800/v1/money/withdrawals/41/cancel -H "Authorization: Bearer <session-token>"
GET/v1/money/movementsKey: READ
The movement history, newest first
The client view of the movements, newest first. States IN: DETECTED, CONFIRMED, FAILED; OUT: REQUESTED, APPROVED, SUBMITTING, SUBMITTED, CONFIRMED, FAILED, CANCELLED. statusText is the client sentence per state. No employee name, internal reason or IP ever reaches this view.
Path and query parameters
Name
In
Type
Required
Description
direction
query
string
no
IN or OUT; absent = both
limit
query
number
no
1 to 200, default 50
before
query
number
no
Keyset cursor: the nextBefore of the previous page (a movement id)
The page size, the cursor, or a direction that is not IN or OUT
4290
429
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
PUT/v1/money/movements/{movementId}/cost-basisSession only
Declare the cost basis of a credited deposit
Declares what coins credited from outside cost (docs/08 revision 50, BROKER-21): the tax statement's FIFO timeline then prices that lot at the declared cost instead of the mark captured when the deposit was credited. Every PUT appends one DECLARED row and the newest wins; nothing is ever edited or deleted. The movement reads carry the effective row as costBasis (source DECLARED | INDICATIVE | NONE, costUsdc, markUsdc, declaredAt; null on a withdrawal and on a USDC deposit) and costBasisDeclarable. A money mutation, excluded for API keys (403 code 4034); no step-up.
Path and query parameters
Name
In
Type
Required
Description
movementId
path
number
yes
The client's own CREDITED crypto deposit of an asset (never a USDC deposit, never a withdrawal)
Request body
Name
Type
Required
Description
costUsdc
string
yes
The acquisition cost of the WHOLE deposit in USDC: a plain decimal, zero or more, with at most 6 decimal places (zero for an airdrop or a gift)
Unknown or another client's movement ("This movement was not found.")
4000
400
Not a credited crypto deposit ("A cost basis can be declared only on a credited deposit."), a USDC deposit ("A USDC deposit has no cost basis to declare."), or the cost shape ("The cost must be a decimal number of USDC, zero or more, with at most 6 decimal places.")
Account, costs and charges, transaction, tax and client assets statements (docs/08 revision 11), plus the live ex-ante cost disclosure. Reads and downloads are key-read; the generation (POST /v1/statements) is a session surface only. Client generations are throttled at 30 per hour per client. Nothing here ever deletes a statement (5 year retention).
GET/v1/statements/costsKey: READ
The live ex-ante cost disclosure
The CURRENT ex-ante disclosure (MiFID II Article 50(2)): per product family the client's own commission schedule (bps and minimum, the assigned scheme else the family default), the funding note, per barrier market the premium share figures from the venue's instrument facts (null when the venue is unreachable), and the ex-ante figures of the client's last 50 placed trades beside what was actually charged. Computed live, never stored.
Response example
{
"code": 0,
"data": {
"currency": "USDC",
"generatedAt": 1787356800000,
"families": [
{ "family": "PERP", "commissionBasis": "NOTIONAL", "commissionBps": "10", "commissionMin": "1.000000", "currency": "USDC", "scheme": "default", "note": "Charged per execution on the executed notional, minimum per order." }
],
"funding": { "note": "Perpetual positions pay or receive the venue's periodic funding at the position held at each settlement; the rate is published per market before it settles (GET /v1/funding) and is not a broker charge." },
"barrierMarkets": [
{ "symbol": "BTC-USD-KO", "knockOutRebateBps": 1000, "barrierPremiumBps": 9000, "strictTakeProfitPremiumBps": 5000, "strictTakeProfitOffered": true }
],
"lastTrades": [
{ "time": 1787300000000, "kind": "PERP", "reference": "BARR-41", "symbol": "BTC-USD-PERP", "side": "BUY", "qty": "0.1", "price": "65000", "filledQty": "0.1", "estimatedCommission": "6.500000", "chargedCommission": "6.500000", "premiumEstimate": null, "premiumCharged": null }
],
"notes": [
"These are the costs and charges shown before you trade (ex-ante, Article 50(2) of Commission Delegated Regulation (EU) 2017/565); the costs and charges statement shows what was actually paid (ex-post).",
"The premium of a barrier contract is the contract's price (a share of the entry-to-barrier distance), settled by the venue with the writer; it is not a broker charge. Third-party payments: none. Currency conversion: none, single ledger currency.",
"Your cash balance is held as USDC; it is applied to the trading venue as USD at one to one and returned to you as USDC."
]
}
}
Errors
Code
HTTP
When
4010
401
No or unknown credential
4290
429
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
The list rows. generatedBy is CLIENT, SYSTEM or EMPLOYEE (an employee's login never crosses to a client). formats says which downloads exist (COSTS has no CSV). version, supersedes and supersededBy carry the reissue chain. The answer is the bare {statements} shape (the route predates the envelope).
Path and query parameters
Name
In
Type
Required
Description
kind
query
string
no
ACCOUNT, COSTS, TRANSACTIONS, TAX or CLIENT_ASSETS; absent = every kind
An unknown kind (reason STATEMENT_KIND) or a bad limit
4290
429
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
Builds the model, renders the PDF (and the CSV where the kind has one) and answers the new row. Statements generation is a session surface: excluded for API keys (403 code 4034). Throttled at 30 generations per client per hour (429, reason STATEMENT_RATE).
Request body
Name
Type
Required
Description
kind
string
yes
ACCOUNT, COSTS, TRANSACTIONS, TAX or CLIENT_ASSETS
preset
string
no
LAST_30_DAYS, LAST_MONTH, THIS_YEAR, LAST_YEAR, CUSTOM, SINCE_LAST_STATEMENT, LAST_QUARTER or THIS_QUARTER
from
string
no
YYYY-MM-DD, with preset CUSTOM (at most 24 months)
The row plus the full statement model (the JSON the PDF renders from). 404 for another client's or an unknown id. The model's shape varies by kind and is not pinned here; the row fields are.
Unknown or another client's id (STATEMENT_NOT_FOUND)
4290
429
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
Unknown or another client's id, or the file is missing
4290
429
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
Unknown or another client's id, or the kind offers no CSV (STATEMENT_FORMAT: "This statement is not available in that format.")
4290
429
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
Staged verification (docs/05 section 3): level MICA unlocks the account, crypto funding and crypto spot; MICA_MIFID adds the MiFID blocks and is asked when the client first trades a MiFID product; bank details only at the first fiat movement. Reads are key-read; EVERY write (block saves, submits, document uploads and removals, identity sessions, appropriateness answers, the company file) is a session surface, excluded for API keys (403 code 4034). The identity webhook is the provider's signed callback, not a client surface.
GET/v1/verificationKey: READ
The verification overview
Contract V4: the level and lifecycle state, every block with status and data (client-editable blocks only), the identity session view, the appropriateness result, the requirements map (action to missing block codes over TRADE:PERP, TRADE:SPOT, TRADE:BARRIER, TRADE:STAKING and the four money actions), the enhanced due diligence status and the MiFID review mode.
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
The gate check for one action: whether it is allowed at the client's level, the required level, the blocks still missing (in wizard order) and one sentence for the client.
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
Validates the block (400 with data.fields keyed by path) and saves it as DRAFT (version + 1). A block under review or approved is locked (409 code 4092); the appropriateness, identity and company blocks are not edited this way. A verification write, excluded for API keys.
Path and query parameters
Name
In
Type
Required
Description
code
path
string
yes
A client-editable block code
Request body
Name
Type
Required
Description
data
object
yes
The block's fields (the object under data, or the body itself); validated per block, at most 64 KB
MICA: every MiCA block complete and the identity check submitted or approved, then the blocks go SUBMITTED and the lifecycle to KYC_PENDING for the compliance decision (a LEGAL client also needs the company file complete). MIFID: needs level MICA, the data blocks complete and the appropriateness PASS or WARN acknowledged; in auto review the level is granted at once when the assessment passed within two attempts.
POST/v1/verification/blocks/BANK_ACCOUNT/submitSession only
Submit the bank details alone
The bank block is asked at the first fiat movement and submitted on its own, outside a level submit: DRAFT with valid data becomes SUBMITTED, which the fiat gate accepts as complete. Idempotent once SUBMITTED or APPROVED.
No saved data ("Save your bank account details first.") or the saved data no longer validates
curl
curl -X POST https://barriers.dev.mtf.perpetuals.com:9800/v1/verification/blocks/BANK_ACCOUNT/submit -H "Authorization: Bearer <session-token>"
POST/v1/verification/blocks/SOURCE_OF_WEALTH/submitSession only
Submit the source of wealth alone
The enhanced due diligence trigger (decision D3): once the deposit threshold is reached the source-of-wealth block is due before the next money movement, whatever the level. A submitted block completes the requirement at once and lands in the compliance review queue.
No saved data ("Save your source of income and wealth details first.")
curl
curl -X POST https://barriers.dev.mtf.perpetuals.com:9800/v1/verification/blocks/SOURCE_OF_WEALTH/submit -H "Authorization: Bearer <session-token>"
GET/v1/verification/documentsKey: READ
The uploaded documents
Every live document with its type, side, file facts and source (CLIENT for an own upload, ONDATO for a document archived from the identity verification, EMPLOYEE for a corporate document our team added to a legal entity's company file; archived and team-added documents cannot be removed).
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
Uploads one document. Caps: 20 live documents in total, 3 per type and side, 30 upload attempts per hour; identity documents and the selfie are locked while the identity check is under review or passed. A verification write, excluded for API keys. Answers 201.
Request body
Name
Type
Required
Description
file
file
yes
multipart/form-data; JPEG, PNG or PDF, at most 8 MB (the format is sniffed by magic bytes, not the extension)
type
string
yes
passport, national_id, drivers_license, residence_permit, utility_bill, bank_statement, w9_form, selfie, other, or a corporate document type
Removes an own upload while the file is not part of the compliance record: identity documents and the selfie are locked with the identity check, everything is locked once the MiCA level is under review or approved, and a document archived from the identity provider is never removable.
Creates (or returns the pending) identity verification session. With the Ondato provider the answer carries the hosted flow url and providerRef (the raw IDV id a native SDK initialises from, docs/08 revision 32); in manual mode the session is decided by compliance. A verification write, excluded for API keys.
Request body
Name
Type
Required
Description
redirectUrl
string
no
Where the hosted flow returns the client afterwards
The current session: status NOT_STARTED, PENDING, SUBMITTED, VERIFIED, FAILED or EXPIRED; sessionUrl only while PENDING; providerRef since docs/08 revision 32. Polling this route also concludes a finished provider session when the webhook has not landed yet.
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
Not a client surface: the identity provider posts session conclusions here, authenticated by its own signature header, never by a client credential (the route is on the public-POST allowlist). Payloads above 256 KB are refused. Documented for completeness only; clients never call it.
Response example
{
"code": 0,
"data": { "ok": true }
}
Errors
Code
HTTP
When
4000
400
A missing or wrong signature, or an unknown session
4000
413
Payload too large
GET/v1/verification/appropriateness/testKey: READ
The current appropriateness questionnaire
The client's view of the current test: questions and options without points or thresholds, plus the warning and acknowledgement texts.
Response example
{
"code": 0,
"data": {
"version": 2,
"questions": [
{
"id": "q1",
"text": "How often have you traded leveraged products in the last year?",
"section": "experience",
"options": [
{ "id": "a", "text": "Never" },
{ "id": "b", "text": "A few times" },
{ "id": "c", "text": "Regularly" }
]
}
],
"sections": null,
"warningText": "Based on your answers, these products may not be appropriate for you.",
"acknowledgeText": "I understand the warning and wish to proceed."
}
}
Errors
Code
HTTP
When
4010
401
No or unknown credential
4290
429
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
The three sentences the DECLARATIONS_MIFID block acknowledges, served in English with their revision and hash (family=DECLARATIONS_MIFID is required; the sentences are an array in the order shown to the client). Render them as served; the block's PUT takes the hash back as text {locale, textHash} beside data.
Response example
{
"code": 0,
"data": {
"family": "DECLARATIONS_MIFID",
"masterVersion": 1,
"asOf": 1790845260000,
"locale": "en",
"requestedLocale": null,
"localeFallback": false,
"availableLocales": ["en"],
"textRevision": 1,
"textHash": "sha256:c04b33d65088c08d79bb62a3333d0741c098c541885e18f92e20ec50ecbaeafe",
"sentences": [
{ "key": "order_execution_policy_consent", "text": "I have read the Order Execution Policy of PM MTF Ltd and I consent to it. ..." },
{ "key": "complex_products_risk_acknowledged", "text": "I have read the Risk Disclosure of PM MTF Ltd, including its section on complex products. ..." },
{ "key": "client_categorisation_notice_acknowledged", "text": "I have received and read the Client Categorisation Notice of PM MTF Ltd. ..." }
]
}
}
Errors
Code
HTTP
When
4000
400
family is absent or names no sentences family we serve (data.error TEXT_FAMILY_UNKNOWN)
4010
401
No or unknown credential
4290
429
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
POST/v1/verification/appropriateness/answersSession only
Submit the appropriateness answers
Scores the answers against the CURRENT test and records the attempt: outcome PASS, WARN (complete once acknowledged) or FAIL (retake after the cooling period, 24 hours by default). A verification write, excluded for API keys.
Request body
Name
Type
Required
Description
version
number
no
The test version answered; a stale one is refused with 409 so the client re-reads the questions
The entity file of a LEGAL account: the entity (with its legal-form kind, COMPANY by default; the decision on your file is its status and date, never a decision text or an officer), the persons (owners, directors, representatives), the corporate documents, the KYB block statuses, what is still missing before a submit, and the ownership chart. requiredDocumentKinds is this entity's own set from the document table (the jurisdiction and the firm's assessment of the file decide what joins the three base kinds); requiredDocumentGroups are the groups the completeness evaluates, a group satisfied by any one of its kinds (the fourth: a beneficial-ownership register extract or the shareholder register); disclosureFloorPct is set for a higher-risk entity (declare every owner from that share; the beneficial-ownership test stays at more than 25 percent), null otherwise; higherRiskReasons is empty on this wire (the reasons of the assessment are the firm's record); uboCarveOut reads LISTED_REGULATED_MARKET when the entity is listed on a regulated market we recognise (no beneficial owner needs declaring), null otherwise; legalFormKinds is the vocabulary. A personal account answers 404.
A personal account ("Your account is a personal account; there is no company file to complete.")
4290
429
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
Saves the entity of the company file (field validation errors are keyed by field in the answer's data). The full field list also carries tradingName, registrationAuthority, incorporationDate, businessAddress, industry, website, taxCountry, isRegulated, regulator, listedExchange and ownershipNote. A verification write, excluded for API keys.
Request body
Name
Type
Required
Description
legalName
string
yes
The registered legal name
registrationNumber
string
yes
The register number
registrationCountry
string
yes
ISO 3166-1 alpha-2; screened like a residency
legalForm
string
no
GmbH, Ltd, ... (a text naming a trust, foundation or partnership form is refused)
legalFormKind
string
no
COMPANY (the default), PARTNERSHIP, TRUST, FOUNDATION or OTHER; only a company can be onboarded at this time
Adds a person row to the company file (the body also takes entityName, entityLei, entityRegistration, countryOfResidence, the id document fields, address, controlType, parentPersonId, position, appointedOn, powers, powerSource, powerLimits, pepDetail, email, phone and linkedClientId). A verification write, excluded for API keys.
Request body
Name
Type
Required
Description
role
string
yes
UBO, DIRECTOR, REPRESENTATIVE or SHAREHOLDER_ENTITY
firstName
string
no
Natural person's first name (entity shareholders use entityName)
Submits the completed company file (entity, owners past the UBO threshold, representatives, required documents) for the compliance review; the MiCA level of a LEGAL account needs it. A verification write, excluded for API keys.
The file is incomplete (the answer names the missing blocks and problems)
4092
409
Already under review or approved
curl
curl -X POST https://barriers.dev.mtf.perpetuals.com:9800/v1/kyb/submit -H "Authorization: Bearer <session-token>"
Support
The help centre and the support requests (docs/08 revision 24). The published FAQ, the suggestions and the feedback are PUBLIC (the help page is reachable signed out); reading requests and case threads is key-read; creating a request, attaching a file and replying are session surfaces, excluded for API keys (403 code 4034).
GET/v1/support/faqPublic
The published FAQ
The published entries of the locale grouped by category in the registry's order. The answer carries an ETag and the route honours If-None-Match with 304 (Cache-Control: no-cache), so a page revalidates cheaply.
Path and query parameters
Name
In
Type
Required
Description
locale
query
string
no
A short lower-case tag (en, de, ...); default en
Response example
{
"code": 0,
"data": {
"locale": "en",
"etag": "W/\"3f9a1b2c3d4e5f6071829304\"",
"count": 24,
"categories": [
{
"code": "GETTING_STARTED",
"name": "Getting started",
"entries": [
{ "id": 1, "category": "GETTING_STARTED", "categoryName": "Getting started", "question": "How do I open an account?", "answer": "Register with your e-mail, verify it and complete the identity verification.", "keywords": ["register", "account"], "updatedAt": 1786000000000 }
]
}
]
}
}
Ranked published entries for the query, throttled per caller. The portal shows these before a request is submitted.
Path and query parameters
Name
In
Type
Required
Description
q
query
string
yes
The draft text, at most 500 characters (empty answers no suggestions)
locale
query
string
no
Default en
limit
query
number
no
At most 5
Response example
{
"code": 0,
"data": {
"query": "withdrawal fee",
"locale": "en",
"suggestions": [
{ "id": 7, "category": "DEPOSITS_WITHDRAWALS", "categoryName": "Deposits and withdrawals", "question": "What are the withdrawal fees?", "answer": "Each asset and network has a fixed withdrawal fee, shown on the Money page before you confirm; the network fee is borne by the broker.", "score": 12 }
]
}
}
Errors
Code
HTTP
When
4290
429
Too many searches ("Please wait a moment before searching the help centre again.")
The category registry of the support form (eleven codes since revision 50; COMPLAINT, listed last, is a request-only category: a request in it opens a high-priority case and files the complaints register entry at the receipt, and no help-centre entry is ever filed under it) and the related reference types a request may name.
Opens a case for the support team (visible at once under My requests) with the client's message as the first thread entry. Throttled per client per hour. A support write, excluded for API keys. A COMPLAINT request that could not record its register entry answers 500 code 5000 ("Your request could not be recorded. Please try again.") with nothing created.
Request body
Name
Type
Required
Description
category
string
yes
A category code from GET /v1/support/categories (COMPLAINT opens a high-priority case and files the complaints register entry at the receipt)
subject
string
yes
At most 200 characters
message
string
yes
At most 4000 characters
relatedRef
object
no
{type, key}: a reference the client names (order, trade, swap, statement, barrier, movement) by the numeric id the portal shows (orderId, venueTradeId, swapId, the statement id, contractId, movementId). Verified once at the create against the client's own records and read back as relatedRef.verified; a reference that is not found is recorded, never refused, and nothing says whether the id exists for someone else
suggestedFaqIds
number[]
no
The FAQ suggestions the portal showed before the submit
The category, subject, message or related reference (the answer names the field)
4290
429
Too many requests in the last hour
5000
500
A COMPLAINT whose register entry could not be written; nothing was created (no case, no request)
curl
curl -X POST https://barriers.dev.mtf.perpetuals.com:9800/v1/support/requests -H "Authorization: Bearer <session-token>" -H "Content-Type: application/json" -d "{\"category\": \"DEPOSITS_WITHDRAWALS\", \"subject\": \"Withdrawal still pending\", \"message\": \"My withdrawal 41 has been pending since yesterday.\"}"
GET/v1/support/requestsKey: READ
My requests, newest first
EVERY case sent to the client (own requests and the cases our teams opened), with the request facts where it is a client request, the unread count, the last message preview, the first-response SLA state (DUE, MET, LATE, OVERDUE or N/A) and the count of the client's own attachments.
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
The case thread (employee messages carry the author "broker", never a login name) plus the request facts and the client's OWN attachments. Reading marks the thread read. Note: because the read marker is a side effect of this GET, a key-authenticated poll also clears the unread badge.
Path and query parameters
Name
In
Type
Required
Description
caseId
path
number
yes
A case sent to this client
Response example
{
"code": 0,
"data": {
"id": 305,
"caseId": 305,
"title": "[Deposits and withdrawals] Withdrawal still pending",
"status": "OPEN",
"closedAt": null,
"messages": [
{ "messageId": 900, "ts": 1787356800000, "direction": "IN", "author": "BARR-00000012", "text": "My withdrawal 41 has been pending since yesterday.", "channel": "PORTAL", "subject": null },
{ "messageId": 901, "ts": 1787360000000, "direction": "OUT", "author": "broker", "text": "Thanks, we are looking into it.", "channel": "PORTAL", "subject": null }
],
"request": { "requestId": 21, "category": "DEPOSITS_WITHDRAWALS", "categoryName": "Deposits and withdrawals", "subject": "Withdrawal still pending", "relatedRef": { "type": "movement", "key": "41", "verified": true }, "suggestedFaqIds": [7], "createdAt": 1787356800000 },
"files": [],
"fromClient": true
}
}
Errors
Code
HTTP
When
4004
404
A case not sent to this client ("This request was not found.")
4290
429
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
POST/v1/support/requests/{caseId}/filesSession only
Attach a file to a request (multipart)
The client's own attachment on a case sent to it: at most 5 files per request from the client, uploads throttled per hour. A case closed within 30 days of its closedAt is reopened by an accepted file (the next read shows status OPEN, closedAt null); a refused file reopens nothing. A support write, excluded for API keys.
Path and query parameters
Name
In
Type
Required
Description
caseId
path
number
yes
The client's own request, OPEN or closed within the last 30 days
Request body
Name
Type
Required
Description
file
file
yes
multipart/form-data, the case-file formats and size caps
Streams the client's OWN attachment (never an employee's file) as an attachment with nosniff.
Path and query parameters
Name
In
Type
Required
Description
caseId
path
number
yes
The client's own request
fileId
path
number
yes
A file the client uploaded on it
Response example
Binary attachment with the stored content type, Content-Disposition: attachment; filename="screenshot.png"
Errors
Code
HTTP
When
4004
404
Not the client's request or not its own file
4290
429
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
Only cases SENT to this client, newest first, with the unread count of employee messages since the client's last thread view. The older sibling of GET /v1/support/requests without the request facts.
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
Title, status and the thread, nothing else; employee messages carry the author "broker". Reading marks the thread read (the same side-effect note as the request thread).
Path and query parameters
Name
In
Type
Required
Description
caseId
path
number
yes
A case sent to this client
Response example
{
"code": 0,
"data": {
"id": 305,
"caseId": 305,
"title": "[Deposits and withdrawals] Withdrawal still pending",
"status": "OPEN",
"closedAt": null,
"messages": [
{ "messageId": 900, "ts": 1787356800000, "direction": "IN", "author": "BARR-00000012", "text": "My withdrawal 41 has been pending since yesterday.", "channel": "PORTAL", "subject": null },
{ "messageId": 901, "ts": 1787360000000, "direction": "OUT", "author": "broker", "text": "Thanks, we are looking into it.", "channel": "PORTAL", "subject": null }
]
}
}
Errors
Code
HTTP
When
4004
404
A case not sent to this client
4290
429
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
Appends the client's message to the thread under its pseudonymous identifier. A case closed within 30 days of its closedAt is reopened by the reply (the next read shows status OPEN, closedAt null); an older closure is refused. A case write, excluded for API keys.
The notification bell (docs/08 revision 24) and the mobile device registry with the push preferences (docs/08 revision 32). Items are DERIVED on read from the stores of record over a 90 day window; the kinds are CASE_REPLY, STATEMENT_READY, VERIFICATION_DECISION, MONEY_MOVEMENT, SWAP_SETTLED, SWAP_DEFAULTED, RESTRICTION_SET and RESTRICTION_LIFTED, plus the later kinds (the barrier alerts, the badges, the pattern digest, INCIDENT_NOTICE and, since docs/08 revision 50, SWAP_WRITTEN_OFF and SWAP_RECOVERED: a staking claim written off by the broker and a credit on a defaulted swap, link /staking, data {resolutionId, symbol, asset, amount, claimAfter}). Reads are key-read; the read marks and the preference write are session surfaces, and the device routes exist for the mobile apps and are excluded for API keys as a whole namespace (403 code 4034).
GET/v1/notificationsKey: READ
The bell: unread count and latest items
The unread count (capped at 500) and the newest items, read or not, within the 90 day window. Titles are English sentences the portal may show as they are; data carries the facts; every link is a portal path. An item id is "KIND:ref".
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
Exactly-once markers: a repeated post is a no-op and marked counts only the NEW ones. One of ids or all is required. Throttled at 60 calls per minute per client. A notification write, excluded for API keys. Answers the new unread count.
Request body
Name
Type
Required
Description
ids
string[]
no
Item ids ("KIND:ref"), at most 200 per call; markers are only written for items of the client's own feed
all
boolean
no
Mark everything through now read (the watermark; a later item is unread again)
One row per notification kind of the bell; an absent stored row reads enabled. The preferences drive the mobile push delivery only, never the bell itself.
The account's key budget for the 60 second window is used up (600 reads or 120 other requests by default, shared across all keys; the figures are on GET /v1/apikeys and the Settings page).
For the mobile apps (docs/08 revision 32): upserts by token (an existing token row rebinds to the calling client and re-enables), at most 20 other device rows per client, 30 registrations per minute. The app registers on every start; rows quiet for 120 days are pruned. The whole /v1/devices namespace is excluded for API keys.
Request body
Name
Type
Required
Description
platform
string
yes
Strictly ANDROID or IOS
token
string
yes
The FCM or APNs device token, 1 to 4096 characters
appBuild
number
no
The app's build number
Response example
{
"code": 0,
"data": { "deviceId": 3 }
}
Errors
Code
HTTP
When
4000
400
The platform ("The platform must be ANDROID or IOS."), the token, or the 20-device cap