Skip to content
barriersbarriers
Sign in
API documentation contents

API documentation

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:

curl https://barriers.dev.mtf.perpetuals.com:9800/v1/tickers

Then read your own account with the key, for example your open positions:

curl -H "X-Api-Key: bak_EXAMPLE" https://barriers.dev.mtf.perpetuals.com:9800/v1/positions

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.

curl -N -H "X-Api-Key: bak_EXAMPLE" "https://barriers.dev.mtf.perpetuals.com:9800/v1/stream?channels=tickers,orderbook:BTC-USD-PERP"

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.

CodeHTTPMeaning
4000400Validation: a malformed body or parameter, or a refused order, cancel or request; msg says what to fix.
4001400Replace: the cancel half was refused and nothing was placed; the old order stands.
4002400Replace: the old order was cancelled but the replacement was refused; data says what happened.
4004404Not found: the id or symbol does not exist or does not belong to your account.
4010401Not signed in: the request carries no valid credential (an unknown or revoked API key answers this too).
4011401The refresh token is unknown, already rotated or revoked; reusing a rotated token revokes its whole family. Sign in again.
4030403Verification gate: the action needs a verification level or block your account does not have; data carries action, level, requiredLevel and missingBlocks.
4031403Registration from your country is not accepted.
4032403Your e-mail address is not verified yet; data carries emailUnverified.
4033400Two-factor step-up: the action needs the current authenticator code; data carries mfaRequired. Send the code and call again.
4034403This API key cannot use this endpoint: the route is outside the key's scope or excluded for keys.
4035403Consent 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.
4038403This 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.
4039403Profile image uploads are not available on this account (a moderation decision); contact support if you have a question.
4040403The pattern scanner is not available on this account (a FAIL appropriateness result while the MiFID level is not granted, or a restriction).
4090409The leverage change would breach your margin; data carries the after figures. Confirm with force to apply it anyway.
4091409The order's leverage hint is not the stored choice any more; nothing was written. Re-read your leverage and send again.
4092409Conflict: the verification section is locked or its state does not allow the action.
4290429Too 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.
5000500The 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.
5020502The trading venue cannot be reached right now; try again shortly.
5030503A 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
NameTypeRequiredDescription
emailstringyesThe sign-in e-mail address.
passwordstringyesThe password; the policy sentence names the requirement when it is refused.
countrystringyesISO-2 country of residence, upper case (screened).
acceptTermsbooleanyesMust be true: "Please accept the terms to open an account."
marketingbooleannoMarketing opt-in, default false.
clientTypestringnoNATURAL (default) or LEGAL (a company registering through its representative).
Response example
{
  "emailVerificationRequired": true
}
Errors
CodeHTTPWhen
4000400Terms not accepted, a malformed e-mail address, the password policy, or a malformed country code (each with its own sentence).
4031403"We do not open accounts for residents of this country." (data carries the country).
4290429More 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
NameTypeRequiredDescription
emailstringyesThe registered address.
codestringyesThe 6-digit code from the mail.
Response example
{
  "ok": true
}
Errors
CodeHTTPWhen
4000400"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).
4290429More 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
NameTypeRequiredDescription
emailstringyesThe registered address.
Response example
{
  "ok": true
}
Errors
CodeHTTPWhen
4290429More 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
NameTypeRequiredDescription
emailstringyesThe sign-in address.
passwordstringyesThe password.
refreshbooleannotrue asks for a refresh-token grant (revision 33); the web portal sends neither field.
deviceNamestringnoNames the device family, at most 64 characters, trimmed.
Response example
{
  "session": {
    "token": "nY1kQ8w3jH5tR2mZ7cX0aB4dE6fG9iJ2kL5nP8rS0uV",
    "expiresAt": 1787436000000,
    "issuedAt": 1787392800000
  },
  "client": {
    "clientId": 41,
    "clientUid": "BARR-00000041",
    "email": "client@example.com",
    "emailVerified": true,
    "mfaEnabled": false,
    "displayName": "Client Example",
    "clientType": "NATURAL",
    "categorization": "RETAIL",
    "lifecycleState": "ACTIVE",
    "verificationLevel": 2,
    "country": "DE",
    "createdAt": 1786300000000,
    "openCases": 0,
    "unreadCaseMessages": 0
  }
}
Errors
CodeHTTPWhen
4010401"Wrong e-mail address or password." (an unknown address and a wrong password read the same).
4032403The password is right but the address is unverified: the answer carries emailUnverified true and the verify-first sentence.
4290429A 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
NameTypeRequiredDescription
mfaTokenstringyesThe token the password step answered.
codestringyesThe 6-digit authenticator code.
refreshbooleannotrue asks for the refresh-token grant (revision 33).
deviceNamestringnoNames the device family, at most 64 characters.
Response example
{
  "session": {
    "token": "nY1kQ8w3jH5tR2mZ7cX0aB4dE6fG9iJ2kL5nP8rS0uV",
    "expiresAt": 1787436000000,
    "issuedAt": 1787392800000
  },
  "client": { "clientId": 41, "clientUid": "BARR-00000041", "email": "client@example.com", "emailVerified": true, "mfaEnabled": true, "displayName": "Client Example", "clientType": "NATURAL", "categorization": "RETAIL", "lifecycleState": "ACTIVE", "verificationLevel": 2, "country": "DE", "createdAt": 1786300000000, "openCases": 0, "unreadCaseMessages": 0 },
  "refreshToken": "qW2eR4tY6uI8oP0aS1dF3gH5jK7lZ9xC1vB3nM5qW7e",
  "refreshExpiresAt": 1795168800000
}
Errors
CodeHTTPWhen
4010401A 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.").
4290429More 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.

Request body
NameTypeRequiredDescription
refreshTokenstringyesThe current refresh token of the device family.
Response example
{
  "code": 0,
  "data": {
    "session": {
      "token": "aB4dE6fG9iJ2kL5nP8rS0uVnY1kQ8w3jH5tR2mZ7cX0",
      "expiresAt": 1787436000000,
      "issuedAt": 1787392800000
    },
    "refreshToken": "zX9cV7bN5mQ3wE1rT8yU6iO4pA2sD0fG2hJ4kL6zX8c",
    "refreshExpiresAt": 1795168800000
  }
}
Errors
CodeHTTPWhen
4011401"Please sign in again." (unknown, already rotated, revoked or past the 90-day family cap; reuse revokes the whole family).
4290429More than 600 per minute per caller address, or more than 20 per minute per client id once the token named one.
curl
curl -X POST https://barriers.dev.mtf.perpetuals.com:9800/v1/auth/refresh -H "Content-Type: application/json" -d '{"refreshToken":"<current refresh token>"}'
POST/v1/auth/logoutSession only

Sign out of the current session

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
NameTypeRequiredDescription
currentPasswordstringyesThe current password.
newPasswordstringyesThe new password (policy checked; must differ from the current one).
codestringnoA current TOTP code; required when two-factor is enabled.
Response example
{
  "ok": true,
  "otherSessionsRevoked": 2
}
Errors
CodeHTTPWhen
4000400The 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).
4033400"Enter the code from your authenticator app to confirm this change." (two-factor enabled, no code sent; data.mfaRequired true).
4290429The step-up lock after 5 wrong authenticator codes (15 minutes, data carries lockedUntil; data.signedOut at the lock: every session is signed out).
curl
curl -X POST https://barriers.dev.mtf.perpetuals.com:9800/v1/auth/password/change -H "Authorization: Bearer <sessionToken>" -H "Content-Type: application/json" -d '{"currentPassword":"old","newPassword":"new-stronger-password"}'
POST/v1/auth/password/forgotPublic

Mail a password reset link

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
NameTypeRequiredDescription
emailstringyesThe sign-in address.
Response example
{
  "ok": true
}
Errors
CodeHTTPWhen
4290429More 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
NameTypeRequiredDescription
tokenstringyesThe token from the reset link.
newPasswordstringyesThe new password (policy checked).
Response example
{
  "ok": true
}
Errors
CodeHTTPWhen
4000400"This password reset link is not valid any more. Request a new one." or the password policy sentence.
4290429More 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).

Response example
[
  {
    "sessionId": 512,
    "current": true,
    "createdAt": 1787392800000,
    "lastSeenAt": 1787392860000,
    "expiresAt": 1787436000000,
    "ip": "203.0.113.7",
    "userAgent": "Mozilla/5.0",
    "deviceName": null,
    "kind": "PASSWORD"
  }
]
curl
curl https://barriers.dev.mtf.perpetuals.com:9800/v1/auth/sessions -H "Authorization: Bearer <sessionToken>"
DELETE/v1/auth/sessions/{id}Session only

Revoke one session

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).

Path and query parameters
NameInTypeRequiredDescription
idpathintegeryesThe sessionId from the list.
Response example
{
  "ok": true
}
Errors
CodeHTTPWhen
4004404"That session was not found."
curl
curl -X DELETE https://barriers.dev.mtf.perpetuals.com:9800/v1/auth/sessions/512 -H "Authorization: Bearer <sessionToken>"
GET/v1/auth/login-historySession only

The account's sign-in history, newest first

Outcomes seen on the wire: OK, BAD_PASSWORD, LOCKED, EMAIL_UNVERIFIED, MFA_REQUIRED, MFA_FAILED, MFA_EXPIRED, MFA_LOCKED, PASSWORD_RESET, REFRESH, REFRESH_REUSE, DEV_LOGIN.

Path and query parameters
NameInTypeRequiredDescription
limitqueryintegernoRows to answer, 1 to 200, default 20.
Response example
[
  { "at": 1787392800000, "ip": "203.0.113.7", "userAgent": "Mozilla/5.0", "outcome": "OK" },
  { "at": 1787306400000, "ip": "203.0.113.7", "userAgent": "Mozilla/5.0", "outcome": "BAD_PASSWORD" }
]
curl
curl "https://barriers.dev.mtf.perpetuals.com:9800/v1/auth/login-history?limit=20" -H "Authorization: Bearer <sessionToken>"
GET/v1/auth/mfaSession only

Two-factor status of the account

Response example
{
  "enabled": true,
  "enabledAt": 1786300000000
}
curl
curl https://barriers.dev.mtf.perpetuals.com:9800/v1/auth/mfa -H "Authorization: Bearer <sessionToken>"
GET/v1/auth/marketingSession only

The marketing e-mail opt-in of the account

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.

Response example
{
  "code": 0,
  "data": {
    "optIn": false
  }
}
curl
curl https://barriers.dev.mtf.perpetuals.com:9800/v1/auth/marketing -H "Authorization: Bearer <sessionToken>"
POST/v1/auth/marketingSession only

Give or withdraw the marketing e-mail opt-in

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
NameTypeRequiredDescription
optInbooleanyestrue to receive marketing e-mail, false to stop it.
Response example
{
  "code": 0,
  "data": {
    "optIn": false
  }
}
Errors
CodeHTTPWhen
4000400"optIn must be true or false" (missing, or not a JSON boolean).
curl
curl -X POST https://barriers.dev.mtf.perpetuals.com:9800/v1/auth/marketing -H "Authorization: Bearer <sessionToken>" -H "Content-Type: application/json" -d '{"optIn":false}'
POST/v1/auth/mfa/setupSession only

Start the TOTP enrolment: a fresh secret and its QR code

A fresh secret, stored encrypted but not yet counting; confirm it on /v1/auth/mfa/enable. Refused while two-factor is already enabled (disable first).

Response example
{
  "secret": "JBSWY3DPEHPK3PXPJBSWY3DPEHPK3PXP",
  "otpauthUri": "otpauth://totp/barriers:client@example.com?secret=JBSWY3DPEHPK3PXPJBSWY3DPEHPK3PXP&issuer=barriers",
  "qrPngBase64": "iVBORw0KGgoAAA..."
}
Errors
CodeHTTPWhen
4000400"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
NameTypeRequiredDescription
codestringyesThe 6-digit code from the authenticator app.
passwordstringyesThe account password.
Response example
{
  "enabled": true,
  "enabledAt": 1787392800000
}
Errors
CodeHTTPWhen
4000400"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."
curl
curl -X POST https://barriers.dev.mtf.perpetuals.com:9800/v1/auth/mfa/enable -H "Authorization: Bearer <sessionToken>" -H "Content-Type: application/json" -d '{"code":"123456","password":"a-strong-password"}'
POST/v1/auth/mfa/disableSession only

Disable two-factor (password plus a current code)

Request body
NameTypeRequiredDescription
codestringyesA current authenticator code.
passwordstringyesThe account password.
Response example
{
  "enabled": false
}
Errors
CodeHTTPWhen
4000400"The password is wrong.", "Two-factor authentication is not enabled on this account." or "The authentication code is not correct."
curl
curl -X POST https://barriers.dev.mtf.perpetuals.com:9800/v1/auth/mfa/disable -H "Authorization: Bearer <sessionToken>" -H "Content-Type: application/json" -d '{"code":"123456","password":"a-strong-password"}'
POST/v1/auth/email/changeSession only

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
NameTypeRequiredDescription
newEmailstringyesThe new sign-in address.
passwordstringyesThe account password.
codestringnoA current TOTP code; required when two-factor is enabled.
Response example
{
  "ok": true,
  "pendingEmail": "new-address@example.com"
}
Errors
CodeHTTPWhen
4000400"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.
4033400"Enter the code from your authenticator app to confirm this change." (two-factor enabled, no code sent).
4290429More 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).
curl
curl -X POST https://barriers.dev.mtf.perpetuals.com:9800/v1/auth/email/change -H "Authorization: Bearer <sessionToken>" -H "Content-Type: application/json" -d '{"newEmail":"new-address@example.com","password":"a-strong-password"}'
POST/v1/auth/email/confirmSession only

Confirm the pending address change with the mailed code

The account now signs in with the confirmed address (verified, since the code reached it); the old address gets a notice.

Request body
NameTypeRequiredDescription
codestringyesThe 6-digit code mailed to the new address.
Response example
{
  "ok": true,
  "email": "new-address@example.com"
}
Errors
CodeHTTPWhen
4000400"The verification code is not correct or has expired." or "There is no e-mail change to confirm."
4290429More than 20 requests per minute per caller address.
curl
curl -X POST https://barriers.dev.mtf.perpetuals.com:9800/v1/auth/email/confirm -H "Authorization: Bearer <sessionToken>" -H "Content-Type: application/json" -d '{"code":"123456"}'

API keys

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
NameTypeRequiredDescription
labelstringyes1 to 64 characters, trimmed; names the key in the list.
scopestringyesExactly READ or TRADE.
totpCodestringnoA current TOTP code; required when two-factor is enabled.
Response example
{
  "code": 0,
  "data": {
    "keyId": 7,
    "apiKey": "bak_nY1kQ8w3jH5tR2mZ7cX0aB4dE6fG9iJ2kL5nP8rS0uV",
    "prefix": "bak_nY1kQ8",
    "label": "trading bot",
    "scope": "TRADE",
    "createdAt": 1787392800000
  }
}
Errors
CodeHTTPWhen
4000400"Please name a label for the key.", "Please choose the READ or TRADE scope." or "You already have ten active API keys. Revoke one first."
4033400"Enter the code from your authenticator app to confirm this change." (two-factor enabled, no totpCode sent; data.mfaRequired true).
4010401No session, or the call carried an API key (the matrix excludes /v1/apikeys for keys).
curl
curl -X POST https://barriers.dev.mtf.perpetuals.com:9800/v1/apikeys -H "Authorization: Bearer <sessionToken>" -H "Content-Type: application/json" -d '{"label":"trading bot","scope":"TRADE"}'
GET/v1/apikeysSession only

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).

Response example
{
  "code": 0,
  "data": {
    "keys": [
      {
        "keyId": 7,
        "label": "trading bot",
        "prefix": "bak_nY1kQ8",
        "scope": "TRADE",
        "createdAt": 1787392800000,
        "lastUsedAt": 1787392860000,
        "revokedAt": null
      }
    ],
    "limits": { "readPerMinute": 600, "tradePerMinute": 120, "windowSeconds": 60 },
    "allowedCidrs": []
  }
}
curl
curl https://barriers.dev.mtf.perpetuals.com:9800/v1/apikeys -H "Authorization: Bearer <sessionToken>"
DELETE/v1/apikeys/{keyId}Session only

Revoke an API key

Idempotent; only the owner's key row is ever touched (a foreign or unknown id is a no-op with the same answer).

Path and query parameters
NameInTypeRequiredDescription
keyIdpathintegeryesThe keyId from the list.
Response example
{
  "code": 0,
  "data": {}
}
curl
curl -X DELETE https://barriers.dev.mtf.perpetuals.com:9800/v1/apikeys/7 -H "Authorization: Bearer <sessionToken>"

Market data

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).

Response example
{
  "instruments": [
    { "symbol": "BTC-USD-PERP", "family": "PERP", "displayName": "Bitcoin Perpetual", "assetClass": "CRYPTO" },
    { "symbol": "BTC-USD", "family": "SPOT", "displayName": "Bitcoin Spot", "assetClass": "CRYPTO" },
    { "symbol": "BTC-USD-KO", "family": "BARRIER", "displayName": "Bitcoin Knock-Out", "assetClass": "CRYPTO" }
  ]
}
curl
curl https://barriers.dev.mtf.perpetuals.com:9800/v1/instruments
GET/v1/tickersPublic

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).

Response example
{
  "asOf": 1787392800000,
  "tickers": [
    {
      "symbol": "BTC-USD-PERP",
      "family": "PERP",
      "displayName": "Bitcoin Perpetual",
      "assetClass": "CRYPTO",
      "lastPrice": "65123.5",
      "markPrice": "65120.0",
      "indexPrice": "65118.2",
      "bestBid": "65120.5",
      "bestAsk": "65126.0",
      "change24h": "512.5",
      "change24hPct": "0.7934",
      "high24h": "65500.0",
      "low24h": "64200.0",
      "volume24h": "1250.75",
      "quoteVolume24h": "81371234.50",
      "openInterest": "310.25",
      "fundingRate": "0.0001",
      "fundingRatePct": "0.01",
      "predictedFundingRate": "0.0001",
      "nextFundingTs": 1787414400000
    }
  ]
}
Errors
CodeHTTPWhen
5020502The venue cannot be reached at all.
curl
curl https://barriers.dev.mtf.perpetuals.com:9800/v1/tickers
GET/v1/tickerPublic

The venue ticker of one market

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.

Path and query parameters
NameInTypeRequiredDescription
symbolquerystringyesThe market symbol, e.g. BTC-USD-PERP.
Response example
{
  "code": 0,
  "msg": "ok",
  "data": {
    "symbol": "BTC-USD-PERP",
    "lastPrice": "65123.5",
    "markPrice": "65120.0",
    "indexPrice": "65118.2",
    "bestBid": "65120.5",
    "bestAsk": "65126.0",
    "volume24h": "1250.75",
    "openInterest": "310.25",
    "fundingRate": "0.0001",
    "fundingRatePct": "0.01",
    "predictedFundingRate": "0.0001",
    "nextFundingTs": 1787414400000
  },
  "asOfSeq": 128734412,
  "ts": 1787392800000
}
Errors
CodeHTTPWhen
4000400"A market symbol is required." or a venue reject (venueCode and venueMsg ride along).
5020502The venue cannot be reached.
curl
curl "https://barriers.dev.mtf.perpetuals.com:9800/v1/ticker?symbol=BTC-USD-PERP"
GET/v1/klinesPublic

Candles for the chart, oldest first

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
NameInTypeRequiredDescription
symbolquerystringyesThe market symbol.
intervalquerystringno1m, 5m, 15m, 30m, 1h, 4h or 1d; default 1m.
fromqueryintegernoInclusive lower bound on the bucket open, Unix ms.
toqueryintegernoExclusive upper bound on the bucket open, Unix ms.
limitqueryintegernoThe newest N inside the bounds, 1 to 1000, default 200.
Response example
{
  "code": 0,
  "data": {
    "symbol": "BTC-USD-PERP",
    "interval": "1m",
    "intervalMs": 60000,
    "klines": [
      { "t": 1787392740000, "open": "65100.0", "high": "65140.0", "low": "65090.5", "close": "65123.5", "volume": "12.4", "trades": 18, "partial": false },
      { "t": 1787392800000, "open": "65123.5", "high": "65130.0", "low": "65119.0", "close": "65127.0", "volume": "3.1", "trades": 5, "partial": true }
    ],
    "hasMore": true,
    "oldestAvailable": 1787332800000,
    "asOf": 1787392815000,
    "asOfSeq": 128734412,
    "ts": 1787392815000
  }
}
Errors
CodeHTTPWhen
4000400"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.
4004404The symbol is not offered here.
5020502The venue cannot be reached.
curl
curl "https://barriers.dev.mtf.perpetuals.com:9800/v1/klines?symbol=BTC-USD-PERP&interval=1m&limit=200"
GET/v1/orderbookPublic

The order book of one market, best level first

Path and query parameters
NameInTypeRequiredDescription
symbolquerystringyesThe market symbol.
depthqueryintegernoLevels per side: 1, 5, 25 or 100 (a value in between rounds up); default 25.
Response example
{
  "symbol": "BTC-USD-PERP",
  "bids": [["65120.5", "0.75"], ["65120.0", "1.20"]],
  "asks": [["65126.0", "0.50"], ["65126.5", "2.00"]],
  "checksum": 3735928559,
  "asOfSeq": 128734412,
  "ts": 1787392800000
}
Errors
CodeHTTPWhen
4000400"A market symbol is required.", a depth that is not a whole number, or a venue reject.
5020502The venue cannot be reached.
curl
curl "https://barriers.dev.mtf.perpetuals.com:9800/v1/orderbook?symbol=BTC-USD-PERP&depth=25"
GET/v1/market-tradesPublic

Recent public trades of one market

side is the aggressor side of the fill.

Path and query parameters
NameInTypeRequiredDescription
symbolquerystringyesThe market symbol.
limitqueryintegernoRows to answer, 1 to 500, default 50.
Response example
{
  "symbol": "BTC-USD-PERP",
  "trades": [
    { "tradeId": 991245, "price": "65123.5", "qty": "0.25", "side": "BUY", "flags": "NORMAL", "ts": 1787392799500 }
  ],
  "asOfSeq": 128734412,
  "ts": 1787392800000
}
Errors
CodeHTTPWhen
4000400"A market symbol is required.", a limit that is not a whole number, or a venue reject.
5020502The venue cannot be reached.
curl
curl "https://barriers.dev.mtf.perpetuals.com:9800/v1/market-trades?symbol=BTC-USD-PERP&limit=50"
GET/v1/index-pricePublic

The index price of one market

ts is the venue's source timestamp of the index tick; indexPrice is null before the market has one.

Path and query parameters
NameInTypeRequiredDescription
symbolquerystringyesThe market symbol.
Response example
{
  "symbol": "BTC-USD-PERP",
  "indexPrice": "65118.2",
  "ts": 1787392799000
}
Errors
CodeHTTPWhen
4000400"A market symbol is required." or a venue reject.
5020502The venue cannot be reached.
curl
curl "https://barriers.dev.mtf.perpetuals.com:9800/v1/index-price?symbol=BTC-USD-PERP"
GET/v1/fundingPublic

The current funding of one market

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.

Path and query parameters
NameInTypeRequiredDescription
symbolquerystringyesThe market symbol.
Response example
{
  "symbol": "BTC-USD-PERP",
  "fundingRate": "0.0001",
  "fundingRatePct": "0.01",
  "predictedFundingRate": "0.0001",
  "predictedFundingRatePct": "0.01",
  "fundingRateAnnualizedPct": "10.95",
  "fundingIntervalHours": 8,
  "settleCcy": "USD",
  "nextFundingTs": 1787414400000,
  "funding": {
    "schedule": "GRID",
    "intervalHours": 8,
    "zone": null,
    "snapshotLocalTime": null,
    "vwapWindow": null,
    "vwapSnapshots": null,
    "nextSettlementTs": 1787414400000,
    "nextSnapshotTs": 1787414400000
  }
}
Errors
CodeHTTPWhen
4000400"A market symbol is required." or a venue reject.
5020502The venue cannot be reached.
curl
curl "https://barriers.dev.mtf.perpetuals.com:9800/v1/funding?symbol=BTC-USD-PERP"
GET/v1/funding/historyPublic

Settled funding rates of one market, newest first

Path and query parameters
NameInTypeRequiredDescription
symbolquerystringyesThe market symbol.
limitqueryintegernoRows to answer, 1 to 1000, default 100.
Response example
{
  "symbol": "BTC-USD-PERP",
  "items": [
    {
      "settleTs": 1787385600000,
      "fundingRate": "0.0001",
      "fundingRatePct": "0.01",
      "fundingRateAnnualizedPct": "10.95",
      "fundingPerUnit": "6.512",
      "markPrice": "65120.0",
      "settleCcy": "USD"
    }
  ]
}
Errors
CodeHTTPWhen
4000400"A market symbol is required.", a limit that is not a whole number, or a venue reject.
5020502The venue cannot be reached.
curl
curl "https://barriers.dev.mtf.perpetuals.com:9800/v1/funding/history?symbol=BTC-USD-PERP&limit=100"
GET/v1/market-infoPublic

The full specification of one market

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.

Path and query parameters
NameInTypeRequiredDescription
symbolquerystringyesThe market symbol.
Response example
{
  "symbol": "BTC-USD-PERP",
  "family": "PERP",
  "displayName": "Bitcoin Perpetual",
  "assetClass": "CRYPTO",
  "enabled": true,
  "baseAsset": "BTC",
  "quoteAsset": "USD",
  "settlementCcy": "USD",
  "tickSize": "0.5",
  "lotSize": "0.001",
  "qtyStep": "0.001",
  "minQty": "0.001",
  "minNotional": "10",
  "contractMultiplier": "1",
  "maxOrderNotional": "1000000",
  "maxPositionNotional": "5000000",
  "priceBandBps": 500,
  "marketMaxSlippageBps": 100,
  "fundingIntervalHours": 8,
  "funding": { "schedule": "GRID", "intervalHours": 8, "zone": null, "snapshotLocalTime": null, "vwapWindow": null, "vwapSnapshots": null, "nextSettlementTs": 1787414400000, "nextSnapshotTs": 1787414400000 },
  "venueStatus": "ACTIVE",
  "venueKind": "PERP",
  "venueMode": "ORDER_BOOK",
  "riskTiers": [
    { "notionalCap": "100000", "imRate": "0.01", "mmRate": "0.005" },
    { "notionalCap": "1000000", "imRate": "0.02", "mmRate": "0.01" }
  ],
  "categorization": "RETAIL",
  "leverageCap": 10,
  "leverage": 10,
  "leverageSchedule": [
    { "categorization": "PROFESSIONAL", "maxLeverage": 25 },
    { "categorization": "RETAIL", "maxLeverage": 10 }
  ],
  "barrierMaxLeverage": null,
  "knockOutRebateBps": null,
  "strictTakeProfitPremiumBps": null,
  "strictTakeProfitOffered": null,
  "barrierPremiumBps": null,
  "barrierExpiry": null,
  "unitLabel": null,
  "kid": null,
  "kidStrict": null,
  "spot": null,
  "feeModel": "commission",
  "commissionBasis": "NOTIONAL",
  "commissionBps": 5,
  "commissionMin": "1.00",
  "commissionCurrency": "USDC",
  "venueFees": { "makerFeeRate": "0.0002", "takerFeeRate": "0.0005", "chargedTo": "broker" }
}
Errors
CodeHTTPWhen
4000400"A market symbol is required."
4004404The symbol is not offered here.
5020502The venue cannot be reached.
curl
curl "https://barriers.dev.mtf.perpetuals.com:9800/v1/market-info?symbol=BTC-USD-PERP"
GET/v1/currenciesPublic

The client currency registry

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).

Response example
{
  "cash": "USDC",
  "currencies": [
    { "code": "USDC", "kind": "CASH", "venueAsset": "USD", "displayDecimals": 2, "scale": 6, "valuationBasis": "CASH" },
    { "code": "BTC", "kind": "ASSET", "venueAsset": "BTC", "displayDecimals": 8, "scale": 8, "valuationBasis": "MARK" },
    { "code": "ETH", "kind": "ASSET", "venueAsset": "ETH", "displayDecimals": 8, "scale": 8, "valuationBasis": "MARK" },
    { "code": "SOL", "kind": "ASSET", "venueAsset": "SOL", "displayDecimals": 6, "scale": 6, "valuationBasis": "MARK" },
    { "code": "BNB", "kind": "ASSET", "venueAsset": "BNB", "displayDecimals": 6, "scale": 6, "valuationBasis": "MARK" },
    { "code": "XRP", "kind": "ASSET", "venueAsset": "XRP", "displayDecimals": 6, "scale": 6, "valuationBasis": "MARK" },
    { "code": "EUR", "kind": "ASSET", "venueAsset": "EUR", "displayDecimals": 2, "scale": 6, "valuationBasis": "MARK" },
    { "code": "USD1", "kind": "ASSET", "venueAsset": "USD1", "displayDecimals": 2, "scale": 6, "valuationBasis": "MARK" }
  ]
}
curl
curl https://barriers.dev.mtf.perpetuals.com:9800/v1/currencies
GET/v1/trading-statusPublic

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.

Response example
{
  "code": 0,
  "data": {
    "asOf": 1787392800000,
    "halted": false,
    "halts": [],
    "restrictions": [],
    "consentGate": null,
    "incidents": []
  }
}
curl
curl https://barriers.dev.mtf.perpetuals.com:9800/v1/trading-status
GET/v1/app-infoPublic

The mobile app version gate

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).

Response example
{
  "android": { "minBuild": 10, "latestBuild": 12, "url": "https://barriers.dev.mtf.perpetuals.com/app/android" },
  "ios": { "minBuild": 0, "latestBuild": 0, "url": "" },
  "notice": "",
  "features": { "demo": true }
}
curl
curl https://barriers.dev.mtf.perpetuals.com:9800/v1/app-info
GET/v1/disclosuresPublic

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
}
curl
curl https://barriers.dev.mtf.perpetuals.com:9800/v1/disclosures

Streaming

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
NameInTypeRequiredDescription
channelsquerystringyesComma list of channels, e.g. tickers,orderbook:BTC-USD-PERP,margin,rfq,notifications.
Response example
retry: 3000

event: tickers
data: {"asOf":1787392800000,"tickers":[{"symbol":"BTC-USD-PERP","family":"PERP","lastPrice":"65123.5","markPrice":"65120.0","bestBid":"65120.5","bestAsk":"65126.0","nextFundingTs":1787414400000}]}

event: notifications
data: {"unread":2,"newestId":"n-1042"}

: hb
Errors
CodeHTTPWhen
4010401No bearer session or API key (a token never rides in the URL).
4000400"Please name the stream channels." (an empty or unknown channel list, a blank orderbook symbol, or more than 2 orderbook entries).
4290429"Too many open streams. Please close one and try again." (more than 3 concurrent streams per client).
curl
curl -N -H "X-Api-Key: bak_EXAMPLE" "https://barriers.dev.mtf.perpetuals.com:9800/v1/stream?channels=tickers,orderbook:BTC-USD-PERP,notifications"

Orders

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
NameTypeRequiredDescription
symbolstringyesThe market symbol, e.g. BTC-USD-PERP.
sidestringyesBUY or SELL.
typestringyesLIMIT, MARKET, STOP_MARKET, STOP_LIMIT, TAKE_PROFIT_MARKET or TAKE_PROFIT_LIMIT.
qtystringyesQuantity as a decimal string, greater than zero, on the market's lot grid.
pricestringnoLIMIT only (a market order takes no price; conditional orders price through the trigger). On the tick grid.
tifstringnoGTC (default), IOC, FOK or POST_ONLY (POST_ONLY with LIMIT only).
reduceOnlybooleannoDefault false; true = the order must reduce the position. Not available on spot markets.
triggerobjectnoConditional types only: {source: LAST | MARK | INDEX, price, direction: AT_OR_ABOVE | AT_OR_BELOW, limitPrice (the *_LIMIT variants only)}.
attachobjectnoLIMIT 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.
ocoGroupstringnoConditional 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.
leverageintegernoThe leverage hint the order was prepared at; a mismatch with the stored choice answers 409 code 4091 with nothing written.
Response example
{
  "orderId": 4182,
  "clOrdId": "BARR-4182",
  "state": "ACKED"
}
Errors
CodeHTTPWhen
4000400Validation or a refused order (the sentence in msg; orderId, clOrdId and state ride along, orderId null when nothing was written).
4030403The verification gate refused an opening order (data: {action, level, requiredLevel, missingBlocks}).
4035403The 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.
4091409The leverage hint no longer matches the stored choice (data: {leverageSent, leverageStored}); nothing was written.
4010401No or unknown client identity.
4034403A READ key called this TRADE route: "This API key cannot use this endpoint."
5020502The trading venue cannot be reached.
4290429The 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/orders -H "X-Api-Key: bak_EXAMPLE" -H "Content-Type: application/json" -d '{"symbol":"BTC-USD-PERP","side":"BUY","type":"LIMIT","qty":"0.5","price":"64250.0","tif":"GTC","leverage":5}'
POST/v1/orders/replaceKey: TRADE

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
NameTypeRequiredDescription
cancelClOrdIdstringyesThe order to cancel: its numeric id ("41") or venue reference ("BARR-41").
orderobjectyesThe replacement, the same body as POST /v1/orders.
Response example
{
  "code": 0,
  "data": {
    "cancelled": true,
    "placed": true,
    "cancelledOrderId": 4182,
    "order": {
      "orderId": 4185,
      "clOrdId": "BARR-4185",
      "symbol": "BTC-USD-PERP",
      "family": "PERP",
      "side": "SELL",
      "type": "STOP_MARKET",
      "qty": "0.5",
      "price": null,
      "state": "PENDING_TRIGGER",
      "reject": null,
      "filledQty": "0",
      "createdAt": 1755855600000,
      "reduceOnly": true,
      "tif": "GTC",
      "trigger": { "source": "MARK", "price": "62800.0", "direction": "AT_OR_BELOW" },
      "updatedAt": 1755855600450,
      "attach": null,
      "ocoGroup": "pos:BTC-USD-PERP:1",
      "parentOrderId": null,
      "parentClOrdId": null,
      "origin": "CLIENT",
      "note": null,
      "replacedBy": null,
      "replaces": "BARR-4182",
      "reservation": null
    }
  }
}
Errors
CodeHTTPWhen
4004404The order to cancel is not the client's: "This order was not found." Nothing happened.
4001400The cancel half was refused; nothing was placed, the old order stands.
4002400The old order was cancelled but the replacement was refused (the answer carries cancelled: true, placed: false and the reason).
4091409The replacement's leverage hint is stale (data: {leverageSent, leverageStored}); nothing happened.
5020502The cancel never reached the venue; nothing happened.
4290429The 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/orders/replace -H "X-Api-Key: bak_EXAMPLE" -H "Content-Type: application/json" -d '{"cancelClOrdId":"BARR-4182","order":{"symbol":"BTC-USD-PERP","side":"SELL","type":"STOP_MARKET","qty":"0.5","tif":"GTC","reduceOnly":true,"trigger":{"source":"MARK","price":"62800.0","direction":"AT_OR_BELOW"},"ocoGroup":"pos:BTC-USD-PERP:1"}}'
DELETE/v1/orders/{orderId}Key: TRADE

Cancel an order

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
NameInTypeRequiredDescription
orderIdpathintegeryesThe router's order id (the numeric part of the BARR- reference).
Response example
{
  "orderId": 4182,
  "clOrdId": "BARR-4182"
}
Errors
CodeHTTPWhen
4004404Not the client's order: "This order was not found."
4000400The order is terminal, not at the venue yet, or the venue refused the cancel (the sentence in msg).
5020502The trading venue cannot be reached.
4290429The 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 DELETE https://barriers.dev.mtf.perpetuals.com:9800/v1/orders/4182 -H "X-Api-Key: bak_EXAMPLE"
GET/v1/orders/openKey: READ

Every live order

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.

Response example
{
  "code": 0,
  "data": [
    {
      "orderId": 4185,
      "clOrdId": "BARR-4185",
      "symbol": "BTC-USD-PERP",
      "family": "PERP",
      "side": "SELL",
      "type": "STOP_MARKET",
      "qty": "0.5",
      "price": null,
      "state": "PENDING_TRIGGER",
      "reject": null,
      "filledQty": "0",
      "createdAt": 1755855600000,
      "reduceOnly": true,
      "tif": "GTC",
      "trigger": { "source": "MARK", "price": "62800.0", "direction": "AT_OR_BELOW" },
      "updatedAt": 1755855600450,
      "attach": null,
      "ocoGroup": "pos:BTC-USD-PERP:1",
      "parentOrderId": null,
      "parentClOrdId": null,
      "origin": "CLIENT",
      "note": null,
      "replacedBy": null,
      "replaces": null,
      "reservation": null
    }
  ]
}
Errors
CodeHTTPWhen
4010401No or unknown client identity.
4290429The 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 https://barriers.dev.mtf.perpetuals.com:9800/v1/orders/open -H "X-Api-Key: bak_EXAMPLE"
GET/v1/ordersKey: READ

Order history (cursor paged)

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
NameInTypeRequiredDescription
limitqueryintegernoPage size, 1 to 500, default 100. A malformed value answers the envelope sentence.
beforequerystringnoThe nextBefore of the previous page: an order id ("41") or its venue reference ("BARR-41").
Response example
{
  "orders": [
    {
      "orderId": 4185,
      "clOrdId": "BARR-4185",
      "symbol": "BTC-USD-PERP",
      "family": "PERP",
      "side": "BUY",
      "type": "LIMIT",
      "qty": "0.5",
      "price": "64250.0",
      "state": "FILLED",
      "reject": null,
      "filledQty": "0.5",
      "createdAt": 1755855600000,
      "reduceOnly": false,
      "tif": "GTC",
      "trigger": null,
      "updatedAt": 1755855700120,
      "attach": {
        "takeProfit": { "price": "66000.0" },
        "stopLoss": { "price": "62800.0" },
        "legs": [4186, 4187]
      },
      "ocoGroup": null,
      "parentOrderId": null,
      "parentClOrdId": null,
      "origin": "CLIENT",
      "note": null,
      "replacedBy": null,
      "replaces": null,
      "reservation": null
    }
  ],
  "hasMore": true,
  "nextBefore": 4185
}
Errors
CodeHTTPWhen
4000400A malformed limit or before value (the envelope sentence).
4010401No or unknown client identity.
4290429The 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 "https://barriers.dev.mtf.perpetuals.com:9800/v1/orders?limit=50" -H "X-Api-Key: bak_EXAMPLE"
GET/v1/tradesKey: READ

Fills (cursor paged)

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
NameInTypeRequiredDescription
limitqueryintegernoPage size, 1 to 500, default 100.
beforequeryintegernoThe nextBefore of the previous page (a trade row id).
Response example
{
  "trades": [
    {
      "id": 90211,
      "venueTradeId": 771204,
      "orderId": 4185,
      "clOrdId": "BARR-4185",
      "symbol": "BTC-USD-PERP",
      "family": "PERP",
      "side": "BUY",
      "price": "64250.0",
      "qty": "0.5",
      "commission": "6.425000",
      "ts": 1755855700100,
      "realizedPnl": "0.000000",
      "notional": null,
      "asset": null
    }
  ],
  "hasMore": false,
  "nextBefore": null
}
Errors
CodeHTTPWhen
4000400A malformed limit or before value.
4010401No or unknown client identity.
4290429The 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 "https://barriers.dev.mtf.perpetuals.com:9800/v1/trades?limit=100" -H "X-Api-Key: bak_EXAMPLE"
GET/v1/leverageKey: READ

The stored leverage choice on a market

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).

Path and query parameters
NameInTypeRequiredDescription
symbolquerystringyesThe market symbol.
Response example
{
  "symbol": "BTC-USD-PERP",
  "leverage": 5,
  "cap": 10,
  "categorization": "RETAIL",
  "chosen": 5
}
Errors
CodeHTTPWhen
4000400"A market symbol is required."
4004404"This market is not offered."
4290429The 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 "https://barriers.dev.mtf.perpetuals.com:9800/v1/leverage?symbol=BTC-USD-PERP" -H "X-Api-Key: bak_EXAMPLE"
PUT/v1/leverageKey: TRADE

Set the leverage on a market

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
NameTypeRequiredDescription
symbolstringyesThe market symbol.
leverageintegeryesThe new leverage, 1 up to the cap.
forcebooleannoApply even when the change would start a margin call or close-out.
Response example
{
  "symbol": "BTC-USD-PERP",
  "leverage": 10,
  "cap": 10,
  "categorization": "RETAIL",
  "chosen": 10
}
Errors
CodeHTTPWhen
4090409The change would breach without force (data: {wouldBreach: true, initialMarginAfter, maintenanceMarginAfter, freeMarginAfter, marginLevelAfter, riskStateAfter}); confirm with force: true to apply anyway.
4000400A missing symbol or leverage, a choice outside [1, cap], or no fresh mark to assess a raising change.
4004404"This market is not offered."
4290429The 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 PUT https://barriers.dev.mtf.perpetuals.com:9800/v1/leverage -H "X-Api-Key: bak_EXAMPLE" -H "Content-Type: application/json" -d '{"symbol":"BTC-USD-PERP","leverage":10}'
POST/v1/leverageKey: TRADE

Set the leverage (POST alias)

The same handler as PUT /v1/leverage, for clients that cannot send PUT: the same body, the same answer, the same 409 code 4090 confirm flow.

Request body
NameTypeRequiredDescription
symbolstringyesThe market symbol.
leverageintegeryesThe new leverage, 1 up to the cap.
forcebooleannoApply even when the change would start a margin call or close-out.
Response example
{
  "symbol": "BTC-USD-PERP",
  "leverage": 10,
  "cap": 10,
  "categorization": "RETAIL",
  "chosen": 10
}
Errors
CodeHTTPWhen
4090409The change would breach without force (the PUT route's answer shape).
4004404"This market is not offered."
4290429The 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/leverage -H "X-Api-Key: bak_EXAMPLE" -H "Content-Type: application/json" -d '{"symbol":"BTC-USD-PERP","leverage":10}'

Positions and margin

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.

Response example
{
  "positions": [
    {
      "symbol": "BTC-USD-PERP",
      "netQty": "0.5",
      "avgEntryPrice": "64250.0",
      "netCost": "32125.0",
      "realizedPnl": "0.000000",
      "updatedAt": 1755855700120
    }
  ]
}
Errors
CodeHTTPWhen
4010401No or unknown client identity.
4290429The 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 https://barriers.dev.mtf.perpetuals.com:9800/v1/positions -H "X-Api-Key: bak_EXAMPLE"
GET/v1/position-historyKey: READ

Fills that realized P&L (cursor paged)

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
NameInTypeRequiredDescription
limitqueryintegernoPage size, 1 to 500, default 100.
beforequeryintegernoThe nextBefore of the previous page (a trade row id).
Response example
{
  "items": [
    {
      "id": 90340,
      "orderId": 4190,
      "clOrdId": "BARR-4190",
      "symbol": "BTC-USD-PERP",
      "side": "SELL",
      "qty": "0.5",
      "price": "65100.0",
      "realizedPnl": "425.000000",
      "ts": 1755912000000
    }
  ],
  "hasMore": false,
  "nextBefore": null
}
Errors
CodeHTTPWhen
4000400A malformed limit or before value.
4010401No or unknown client identity.
4290429The 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 "https://barriers.dev.mtf.perpetuals.com:9800/v1/position-history?limit=50" -H "X-Api-Key: bak_EXAMPLE"
GET/v1/marginKey: READ

The margin document (the risk view)

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.

Response example
{
  "clientId": 12,
  "equity": "25412.380000",
  "cash": "24800.000000",
  "unrealizedPnl": "612.380000",
  "reserved": "0.000000",
  "initialMargin": "6425.000000",
  "maintenanceMargin": "3212.500000",
  "positionMargin": "6425.000000",
  "orderMargin": "0.000000",
  "freeMargin": "18987.380000",
  "marginLevel": "395.52",
  "riskState": "NORMAL",
  "categorization": "RETAIL",
  "marksFresh": true,
  "closeOutPct": "50",
  "marginCallPct": "100",
  "positions": [
    {
      "symbol": "BTC-USD-PERP",
      "netQty": "0.5",
      "avgEntryPrice": "64250.0",
      "mark": "65474.76",
      "markFresh": true,
      "unrealizedPnl": "612.380000",
      "initialMargin": "6425.000000",
      "roiPct": "9.53",
      "leverage": 5,
      "realizedPnl": "0.000000",
      "estLiquidationPrice": "27650.24"
    }
  ],
  "openOrders": 1,
  "barrierMargin": "0.000000",
  "barrierUnrealizedPnl": "0.000000",
  "barrierMarksFresh": true,
  "pendingAcceptMargin": "0.000000",
  "closeOutEquity": "25412.380000",
  "barriers": []
}
Errors
CodeHTTPWhen
4010401No or unknown client identity.
5030503"The margin service is unavailable right now. Please try again shortly."
4290429The 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 https://barriers.dev.mtf.perpetuals.com:9800/v1/margin -H "X-Api-Key: bak_EXAMPLE"
POST/v1/margin/previewKey: TRADE

Pre-trade margin preview

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
NameTypeRequiredDescription
symbolstringyesThe market symbol.
sidestringyesBUY or SELL.
typestringyesThe order type, as on POST /v1/orders.
tifstringnoGTC (default), IOC, FOK or POST_ONLY.
qtystringyesQuantity as a decimal string.
pricestringnoLIMIT only, as on POST /v1/orders.
triggerobjectnoConditional types only, as on POST /v1/orders.
reduceOnlybooleannoPreview a reduce-only order.
leverageintegernoPreview at this leverage instead of the stored choice; not persisted.
Response example
{
  "accepted": true,
  "reason": null,
  "reservation": null,
  "orderValue": "32125.000000",
  "marginRequired": "6425.000000",
  "leverage": 5,
  "freeMarginBefore": "18987.380000",
  "freeMarginAfter": "12523.760000",
  "marginLevelAfter": "197.65",
  "estLiquidationPrice": "51856.40",
  "equityBefore": "25412.380000",
  "initialMarginBefore": "6425.000000",
  "initialMarginAfter": "12850.000000",
  "estimatedCommission": "6.425000",
  "slippageAllowance": "32.120000",
  "estimatedCosts": "38.545000"
}
Errors
CodeHTTPWhen
4000400Validation (the sentence in msg; the body also carries accepted: false and reason).
4004404"This market is not offered."
5030503The margin service is unavailable.
4010401No or unknown client identity.
4290429The 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/margin/preview -H "X-Api-Key: bak_EXAMPLE" -H "Content-Type: application/json" -d '{"symbol":"BTC-USD-PERP","side":"BUY","type":"LIMIT","qty":"0.5","price":"64250.0"}'
GET/v1/positions/fundingKey: READ

Funding attribution per open position

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).

Response example
{
  "asOf": 1787390000000,
  "items": [
    {
      "symbol": "BTC-USD-PERP",
      "openLotsFunding": "-0.412512",
      "accFunding": "-0.031200",
      "closedLotsFunding": "-1.204487",
      "settleCcy": "USDC",
      "chargedTotal": "-1.616999",
      "ok": true,
      "message": null
    }
  ]
}
Errors
CodeHTTPWhen
4010401No or unknown credentials.
4290429The 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 -H 'X-Api-Key: bak_EXAMPLE' https://barriers.dev.mtf.perpetuals.com:9800/v1/positions/funding
GET/v1/funding-history-mineKey: READ

The client's funding payment history

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).

Path and query parameters
NameInTypeRequiredDescription
limitqueryintegernoPage size, 1 to 500 (default 100).
beforequerystringnoThe nextBefore cursor of the previous page.
Response example
{
  "items": [
    {
      "id": 88213,
      "symbol": "BTC-USD-PERP",
      "settledAt": 1787356800000,
      "qty": "0.0500",
      "side": "LONG",
      "fundingPerUnit": "-0.775420",
      "amount": "-0.038771",
      "currency": "USDC"
    }
  ],
  "hasMore": false,
  "nextBefore": null,
  "note": null
}
Errors
CodeHTTPWhen
4000400A limit outside 1..500 or a cursor that is not valid.
4010401No or unknown credentials.
4290429The 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 -H 'X-Api-Key: bak_EXAMPLE' 'https://barriers.dev.mtf.perpetuals.com:9800/v1/funding-history-mine?limit=50'

Barrier RFQs

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
NameTypeRequiredDescription
symbolstringyesA barrier market, e.g. BTC-USD-KO.
sidestringyesBUY (long, knock-out below the entry) or SELL (short, knock-out above); LONG / SHORT accepted.
qtystringyesQuantity 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.
barrierobjectyes{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)}.
expiresInMsintegernoThe quoting window in ms (default 60000, 1 s to 24 h).
autoAcceptbooleannoDefault true: the router accepts the first acceptable live quote itself; false keeps the manual accept flow.
patternSetupIdintegernoThe pattern setup id the ticket was pre-filled from; stored as the acted-upon record, never affects pricing or routing.
sharedSetupIdintegernoThe 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.
Response example
{
  "code": 0,
  "data": {
    "rfqId": 311,
    "clientId": 12,
    "clientUid": "BARR-00000012",
    "venueRfqId": 5207,
    "clOrdId": "BARRFQ-311",
    "symbol": "BTC-USD-KO",
    "side": "BUY",
    "direction": "LONG",
    "qty": "1.0",
    "barrierLevel": "63000.0",
    "takeProfit": "66500.0",
    "expiryTs": 1755942000000,
    "expiryDefaulted": false,
    "expiresAt": 1755855760000,
    "expiresInMs": 59200,
    "state": "OPEN",
    "createdAt": 1755855700000,
    "updatedAt": 1755855700000,
    "rejectReason": null,
    "accepting": false,
    "autoAccept": true,
    "autoAcceptNote": null,
    "closeContractId": null,
    "contractEntry": null,
    "acceptedQuoteId": null,
    "contractId": null,
    "entryPrice": null,
    "commission": "0.000000",
    "maxLossAtRequest": "2222.130000",
    "premium": "2222.130000",
    "markAtRequest": "65469.0",
    "impliedLeverage": "26.51",
    "potentialProfit": "1031.000000",
    "strictTakeProfit": false,
    "premiumBps": 9000,
    "quotes": [],
    "currency": "USDC"
  }
}
Errors
CodeHTTPWhen
4000400Validation, 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.
4030403The verification gate (the missing blocks in data).
4035403The 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}).
4004404"This market is not offered."
5020502The venue could not be reached (data.rfqId names the row; it stays OPEN and the poll resolves it).
4290429The 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 -H "X-Api-Key: bak_EXAMPLE" -H "Content-Type: application/json" -d '{"symbol":"BTC-USD-KO","side":"BUY","qty":"1.0","barrier":{"level":"63000.0","takeProfit":"66500.0"},"autoAccept":true}'
POST/v1/rfq/previewKey: TRADE

Preview a barrier RFQ (nothing written)

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
NameTypeRequiredDescription
symbolstringyesA barrier market.
sidestringyesBUY or SELL (LONG / SHORT accepted).
qtystringyesQuantity as a decimal string.
barrierobjectyesAs on POST /v1/rfq.
expiresInMsintegernoThe quoting window; validated, answered as accepted: false when out of bounds.
Response example
{
  "code": 0,
  "data": {
    "accepted": true,
    "reason": null,
    "symbol": "BTC-USD-KO",
    "side": "BUY",
    "direction": "LONG",
    "qty": "1.0",
    "barrierLevel": "63000.0",
    "takeProfit": "66500.0",
    "mark": "65469.0",
    "maxLoss": "2222.130000",
    "premium": "2222.130000",
    "estimatedCommission": "22.221300",
    "estimatedCloseCommission": "22.221300",
    "requiredFreeMargin": "2266.572600",
    "freeMarginBefore": "18987.380000",
    "freeMarginAfter": "16720.807400",
    "impliedLeverage": "26.51",
    "potentialProfit": "1031.000000",
    "maxLeverage": "1000",
    "expiryTs": 1755942000000,
    "expiryDefaulted": false,
    "strictTakeProfit": false,
    "premiumBps": 9000,
    "knockOutRebateBps": 1000,
    "strictTakeProfitPremiumBps": 5000,
    "strictTakeProfitOffered": true,
    "currency": "USDC"
  }
}
Errors
CodeHTTPWhen
4000400Validation (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.
4030403The verification gate.
4035403A 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.
4004404"This market is not offered."
4290429The 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/preview -H "X-Api-Key: bak_EXAMPLE" -H "Content-Type: application/json" -d '{"symbol":"BTC-USD-KO","side":"BUY","qty":"1.0","barrier":{"level":"63000.0"}}'
GET/v1/rfq/mineKey: READ

My requests (the live board)

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).

Response example
{
  "code": 0,
  "data": {
    "rfqs": [
      {
        "rfqId": 311,
        "symbol": "BTC-USD-KO",
        "side": "BUY",
        "direction": "LONG",
        "qty": "1.0",
        "barrierLevel": "63000.0",
        "takeProfit": "66500.0",
        "state": "OPEN",
        "expiresAt": 1755855760000,
        "expiresInMs": 31500,
        "autoAccept": true,
        "autoAcceptNote": null,
        "closeContractId": null,
        "premiumBps": 9000,
        "strictTakeProfit": false,
        "quotes": [
          {
            "quoteId": 902,
            "price": "65471.5",
            "qty": "1.0",
            "validUntil": 1755855740000,
            "validForMs": 11500,
            "impliedMaxLoss": "2224.350000",
            "impliedPremium": "2224.350000",
            "impliedLeverage": "26.49",
            "impliedPotentialProfit": "1028.500000",
            "impliedPnl": null,
            "state": "LIVE",
            "acceptable": true,
            "notAcceptableReason": null
          }
        ],
        "currency": "USDC"
      }
    ]
  }
}
Errors
CodeHTTPWhen
4010401No or unknown client identity.
4290429The 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 https://barriers.dev.mtf.perpetuals.com:9800/v1/rfq/mine -H "X-Api-Key: bak_EXAMPLE"
GET/v1/rfq/{rfqId}Key: READ

One request with its quotes

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.

Path and query parameters
NameInTypeRequiredDescription
rfqIdpathintegeryesThe router's request id.
Response example
{
  "code": 0,
  "data": {
    "rfqId": 311,
    "symbol": "BTC-USD-KO",
    "side": "BUY",
    "direction": "LONG",
    "state": "ACCEPTED",
    "acceptedQuoteId": 902,
    "contractId": 4711,
    "entryPrice": "65471.5",
    "commission": "22.243500",
    "premiumBps": 9000,
    "quotes": [],
    "currency": "USDC"
  }
}
Errors
CodeHTTPWhen
4004404Not the client's request.
4290429The 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 https://barriers.dev.mtf.perpetuals.com:9800/v1/rfq/311 -H "X-Api-Key: bak_EXAMPLE"
POST/v1/rfq/{rfqId}/acceptKey: TRADE

Accept a quote (opens the contract)

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.

Path and query parameters
NameInTypeRequiredDescription
rfqIdpathintegeryesThe router's request id.
Request body
NameTypeRequiredDescription
quoteIdintegeryesThe quote to accept.
Response example
{
  "code": 0,
  "data": {
    "rfqId": 311,
    "venueRfqId": 5207,
    "quoteId": 902,
    "contractId": 4711,
    "tradeId": 771310,
    "symbol": "BTC-USD-KO",
    "side": "BUY",
    "direction": "LONG",
    "qty": "1.0",
    "entry": "65471.5",
    "barrierLevel": "63000.0",
    "takeProfit": "66500.0",
    "expiryTs": 1755942000000,
    "maxLoss": "2224.350000",
    "premium": "2224.350000",
    "leverage": "26.49",
    "potentialProfit": "1028.500000",
    "commission": "22.243500",
    "strictTakeProfit": false,
    "premiumBps": 9000,
    "ts": 1755855728000,
    "currency": "USDC"
  }
}
Errors
CodeHTTPWhen
4000400The 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).
4035403The TERMS re-consent gate (data: {doc, currentVersion, gateFrom}) on the accept of an OPENING request; the accept of a close request is never gated.
4004404An unknown request, or the quote is gone or expired.
5020502The venue could not be reached; the accept stays marked in flight (data: {rfqId, quoteId, accepting: true}) and the poll resolves it.
4290429The 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/accept -H "X-Api-Key: bak_EXAMPLE" -H "Content-Type: application/json" -d '{"quoteId":902}'
POST/v1/rfq/{rfqId}/cancelKey: TRADE

Cancel a request

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.

Path and query parameters
NameInTypeRequiredDescription
rfqIdpathintegeryesThe router's request id.
Response example
{
  "code": 0,
  "data": {
    "rfqId": 311,
    "state": "CANCELLED"
  }
}
Errors
CodeHTTPWhen
4000400The request is not open, an accept is in flight, or the venue refused the cancel (data carries venueCode).
4004404Not the client's request.
5020502The venue could not be reached.
4290429The 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
NameInTypeRequiredDescription
statequerystringnoopen (default) or history.
limitqueryintegernoHistory paging: 1 to 500, default 100.
beforequeryintegernoHistory paging: the nextBefore of the previous page (a contract id).
Response example
{
  "code": 0,
  "data": {
    "barriers": [
      {
        "contractId": 4711,
        "symbol": "BTC-USD-KO",
        "side": "LONG",
        "qty": "1.0",
        "entry": "65471.5",
        "barrierLevel": "63000.0",
        "takeProfit": "66500.0",
        "expiryTs": 1755942000000,
        "expiresInMs": 86272000,
        "state": "OPEN",
        "openedAt": 1755855728000,
        "closedAt": null,
        "settlePrice": null,
        "pnl": null,
        "mark": "65612.0",
        "markFresh": true,
        "distanceAbs": "2612.0",
        "distancePct": "3.98",
        "distanceToTpAbs": "888.0",
        "distanceToTpPct": "1.35",
        "unrealizedPnl": "140.500000",
        "requirement": "2083.850000",
        "maxLoss": "2224.350000",
        "premium": "2224.350000",
        "potentialProfit": "1028.500000",
        "leverage": "26.49",
        "commission": "22.243500",
        "closeCommission": null,
        "estimatedCloseCommission": "22.243500",
        "notional": "65612.000000",
        "venueRfqId": 5207,
        "rfqId": 311,
        "closeReason": null,
        "closeRfqId": null,
        "strictTakeProfit": false,
        "premiumBps": 9000,
        "currency": "USDC"
      }
    ],
    "hasMore": false,
    "nextBefore": null,
    "currency": "USDC"
  }
}
Errors
CodeHTTPWhen
4000400An unknown state filter or a malformed limit / before value.
4010401No or unknown client identity.
4290429The 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 "https://barriers.dev.mtf.perpetuals.com:9800/v1/barriers?state=open" -H "X-Api-Key: bak_EXAMPLE"
POST/v1/barriers/{contractId}/closeKey: TRADE

Close a contract early by RFQ

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
NameInTypeRequiredDescription
contractIdpathintegeryesThe client's OPEN contract.
Request body
NameTypeRequiredDescription
autoAcceptbooleannoAccept the first acceptable quote of the close request automatically (default true).
expiresInMsintegernoThe quoting window in ms (default 60000, 1 s to 24 h).
Response example
{
  "code": 0,
  "data": {
    "rfqId": 320,
    "symbol": "BTC-USD-KO",
    "side": "SELL",
    "direction": "SHORT",
    "qty": "1.0",
    "barrierLevel": "63000.0",
    "takeProfit": "66500.0",
    "state": "OPEN",
    "autoAccept": true,
    "closeContractId": 4711,
    "contractEntry": "65471.5",
    "premium": null,
    "impliedLeverage": null,
    "potentialProfit": null,
    "strictTakeProfit": false,
    "premiumBps": 9000,
    "quotes": [],
    "currency": "USDC"
  }
}
Errors
CodeHTTPWhen
4004404The contract does not exist or is not the client's.
4000400The 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.
5020502The venue could not be reached (the row stays OPEN, the poll resolves it).
4290429The 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/barriers/4711/close -H "X-Api-Key: bak_EXAMPLE" -H "Content-Type: application/json" -d '{}'

Staking

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.

Response example
{
  "code": 0,
  "data": {
    "markets": [
      {
        "symbol": "USD-TBILL-SWAP",
        "name": "USD Fixed Yield Contract",
        "asset": "USDC",
        "venueAsset": "USD",
        "enabled": true,
        "minTenorDays": 7,
        "maxTenorDays": 364,
        "maxRateBps": 2000,
        "maxRatePct": "20.00",
        "minQty": "100",
        "qtyStep": "1",
        "maxQty": "1000000",
        "takerFeeRate": "0.0002",
        "displayDecimals": 2,
        "commissionRewardBps": "1000",
        "family": "STAKING",
        "usualTenors": [28, 91, 182, 364]
      }
    ],
    "disclosure": "...",
    "commissionRewardBps": "1000"
  }
}
Errors
CodeHTTPWhen
4010401No or unknown client identity.
5020502The trading venue cannot be reached.
4290429The 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 https://barriers.dev.mtf.perpetuals.com:9800/v1/staking/markets -H "X-Api-Key: bak_EXAMPLE"
GET/v1/staking/offersKey: READ

The offers board

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
NameInTypeRequiredDescription
assetquerystringnoOnly offers in this asset (client currency or venue asset code).
symbolquerystringnoOnly offers on this market.
Response example
{
  "code": 0,
  "data": {
    "offers": [
      {
        "offerId": 6103,
        "symbol": "USD-TBILL-SWAP",
        "name": "USD Fixed Yield Contract",
        "asset": "USDC",
        "venueAsset": "USD",
        "rateBps": 450,
        "ratePct": "4.50",
        "tenorDays": 91,
        "qty": "100000",
        "remainingQty": "62500",
        "minQty": "100",
        "expiryTs": 1755942000000,
        "createdTs": 1755855000000,
        "rewardPerUnit": "0.011219",
        "commissionRewardBps": "1000"
      }
    ],
    "asOf": 1755855730000,
    "stale": false,
    "disclosure": "...",
    "commissionRewardBps": "1000"
  }
}
Errors
CodeHTTPWhen
4010401No or unknown client identity.
5020502The trading venue cannot be reached.
4290429The 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 "https://barriers.dev.mtf.perpetuals.com:9800/v1/staking/offers?asset=USDC" -H "X-Api-Key: bak_EXAMPLE"
POST/v1/staking/{offerId}/acceptKey: TRADE

Take an offer (opens the swap)

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
NameInTypeRequiredDescription
offerIdpathintegeryesThe offer on the board.
Request body
NameTypeRequiredDescription
qtystringyesThe size to take, in the market's asset, on the venue's lot grid.
Response example
{
  "code": 0,
  "data": {
    "swapId": 88,
    "contractId": 412,
    "tradeId": 771412,
    "offerId": 6103,
    "symbol": "USD-TBILL-SWAP",
    "name": "USD Fixed Yield Contract",
    "asset": "USDC",
    "venueAsset": "USD",
    "displayDecimals": 2,
    "qty": "10000",
    "notional": "10000.00",
    "rateBps": 450,
    "ratePct": "4.50",
    "tenorDays": 91,
    "reward": "112.19",
    "fee": "2.00",
    "expectedPayout": "10112.19",
    "state": "OPEN",
    "openedTs": 1755855740000,
    "maturityTs": 1763718140000,
    "closedTs": null,
    "paid": "0.00",
    "outstanding": "0.00",
    "claim": "0.00",
    "writtenOff": "0.00",
    "recovered": "0.00",
    "commission": "0.00",
    "commissionRewardBps": "1000",
    "estimatedCommission": "11.22",
    "settleReason": null,
    "rejectReason": null,
    "ts": 1755855740000,
    "createdAt": 1755855740000
  }
}
Errors
CodeHTTPWhen
4000400The size rules, the venue facts unavailable, a disabled market, the account state, or the balance short of notional plus fee.
4030403The verification gate (the missing blocks in data).
4035403The TERMS re-consent gate (data: {doc, currentVersion, gateFrom}): a take while the acceptance of the current Terms version is pending past the gate date.
4004404The market is not offered or the offer is no longer on the board.
5020502The venue could not be reached; the row stays ACCEPTING and the poll resolves it.
4290429The 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/staking/6103/accept -H "X-Api-Key: bak_EXAMPLE" -H "Content-Type: application/json" -d '{"qty":"10000"}'
GET/v1/staking/swapsKey: READ

My swaps (cursor paged)

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.

Path and query parameters
NameInTypeRequiredDescription
statequerystringnolive (default), history or all.
limitqueryintegernoPage size, 1 to 500, default 100.
beforequeryintegernoThe nextBefore of the previous page (a swap id).
Response example
{
  "code": 0,
  "data": {
    "swaps": [
      {
        "swapId": 88,
        "contractId": 412,
        "offerId": 6103,
        "symbol": "USD-TBILL-SWAP",
        "asset": "USDC",
        "qty": "10000",
        "notional": "10000.00",
        "rateBps": 450,
        "ratePct": "4.50",
        "tenorDays": 91,
        "reward": "112.19",
        "expectedPayout": "10112.19",
        "state": "OPEN",
        "openedTs": 1755855740000,
        "maturityTs": 1763718140000,
        "paid": "0.00",
        "outstanding": "0.00",
        "claim": "0.00",
        "commission": "0.00",
        "commissionRewardBps": "1000"
      }
    ],
    "hasMore": false,
    "nextBefore": null
  }
}
Errors
CodeHTTPWhen
4000400An unknown state filter or a malformed limit / before value.
4010401No or unknown client identity.
4290429The 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 "https://barriers.dev.mtf.perpetuals.com:9800/v1/staking/swaps?state=live" -H "X-Api-Key: bak_EXAMPLE"
GET/v1/staking/swaps/{id}Key: READ

One swap

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).

Path and query parameters
NameInTypeRequiredDescription
idpathintegeryesThe venue contract id (or the router's swap id).
Response example
{
  "code": 0,
  "data": {
    "swapId": 88,
    "contractId": 412,
    "symbol": "USD-TBILL-SWAP",
    "asset": "USDC",
    "state": "MATURED",
    "paid": "10112.19",
    "outstanding": "0.00",
    "claim": "0.00",
    "commission": "11.22",
    "settleReason": "MATURITY",
    "ts": 1763718150000
  }
}
Errors
CodeHTTPWhen
4004404No swap of the client carries this id.
4290429The 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 https://barriers.dev.mtf.perpetuals.com:9800/v1/staking/swaps/412 -H "X-Api-Key: bak_EXAMPLE"

Pattern scanner

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
NameInTypeRequiredDescription
assetClassquerystringnoFilter: the instrument's asset class (e.g. CRYPTO, INDEX, FX).
timeframequerystringnoFilter: 1d, 1h, 15m or 5m.
directionquerystringnoFilter: LONG or SHORT.
statequerystringnoFilter: the setup state (e.g. FORMED, BREAKOUT).
symbolquerystringnoFilter: one market symbol.
limitqueryintegerno1 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
CodeHTTPWhen
4000400An unknown filter value (the sentence names the field).
4010401No or unknown client identity.
4040403The pattern scanner is not available on this account (a FAIL appropriateness result while the MiFID level is not granted, or a restriction).
4290429The 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 "https://barriers.dev.mtf.perpetuals.com:9800/v1/patterns?timeframe=1h&direction=LONG" -H "X-Api-Key: bak_EXAMPLE"
GET/v1/patterns/{id}Key: READ

One setup with its chart window

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
NameInTypeRequiredDescription
idpathintegeryesThe setup id.
candlesqueryintegernoThe 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
CodeHTTPWhen
4004404An unknown setup id.
4010401No or unknown client identity.
4040403The pattern scanner is not available on this account (a FAIL appropriateness result while the MiFID level is not granted, or a restriction).
4290429The 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 "https://barriers.dev.mtf.perpetuals.com:9800/v1/patterns/5120?candles=120" -H "X-Api-Key: bak_EXAMPLE"
GET/v1/patterns/statusKey: READ

Scanner status

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
CodeHTTPWhen
4010401No or unknown client identity.
4290429The 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 https://barriers.dev.mtf.perpetuals.com:9800/v1/patterns/status -H "X-Api-Key: bak_EXAMPLE"
POST/v1/patterns/{id}/sentimentSession only

Vote plays out or fails

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.

Path and query parameters
NameInTypeRequiredDescription
idpathintegeryesThe setup id.
Request body
NameTypeRequiredDescription
variantstringyes"confirm" (plays out) or "fade" (fails).
Response example
{
  "code": 0,
  "data": { "confirm": 23, "fade": 41, "total": 64, "myVote": "fade" }
}
Errors
CodeHTTPWhen
4000400An unknown variant, or the setup is no longer ACTIVE.
4004404An unknown setup id.
4010401No or unknown client identity.
4040403The 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.

Response example
{
  "code": 0,
  "data": {
    "windowDays": 30,
    "rows": [
      { "pattern": "DOUBLE_TOP", "patternName": "Double top", "timeframe": "15m", "symbol": "BTC-USD-KO", "playedOut": 12, "failed": 9, "expired": 3, "rate": 0.57 }
    ]
  }
}
Errors
CodeHTTPWhen
4010401No or unknown client identity.
4040403The pattern scanner is not available on this account (a FAIL appropriateness result while the MiFID level is not granted, or a restriction).
4290429The 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 https://barriers.dev.mtf.perpetuals.com:9800/v1/patterns/stats/symbols -H "X-Api-Key: bak_EXAMPLE"
GET/v1/patterns/historyKey: READ

Past setups (the record of recommendations)

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
CodeHTTPWhen
4000400timeframe is not one of 1d, 1h, 15m or 5m, or direction is not BULLISH or BEARISH.
4010401No or unknown client identity.
4040403The pattern scanner is not available on this account (a FAIL appropriateness result while the MiFID level is not granted, or a restriction).
4290429The 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 "https://barriers.dev.mtf.perpetuals.com:9800/v1/patterns/history?symbol=BTC-USD-KO&limit=50" -H "X-Api-Key: bak_EXAMPLE"

Demo trading

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.

Response example
{
  "code": 0,
  "data": {
    "wallet": { "balance": "10000.00", "currency": "USDC", "alias": "Trader-3F2A", "resets": 0, "startingBalance": "10000.00", "ageConfirmedAt": null },
    "open": [
      {
        "id": 12, "symbol": "BTC-USD-KO", "side": "LONG", "qty": "1", "entry": "65400.0", "barrier": "64700.0",
        "takeProfit": "66800.0", "strict": false, "expiryTs": null, "premium": "630.000000", "premiumBps": 9000,
        "potentialProfit": "1400.000000", "openedAt": 1755855750000, "state": "OPEN",
        "mark": "65500.0", "distancePct": "1.22", "distanceToTpPct": "1.98", "unrealizedPnl": "90.000000"
      }
    ],
    "history": []
  }
}
Errors
CodeHTTPWhen
4010401No or unknown client identity.
5030503The demo account is switched off on this server
4290429The 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
NameTypeRequiredDescription
symbolstringyesA barrier (KO) market.
sidestringyesBUY or LONG (knock-out below), SELL or SHORT (knock-out above).
qtystringyesQuantity 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.
barrierstringyesThe knock-out level, loss side of the entry.
takeProfitstringyesThe take profit, profit side of the entry.
strictbooleannoStrict take profit (pays only on a touch); needs expiryTs.
expiryTsintegernoUnix ms, at most 8 hours ahead; omit for open ended.
patternSetupIdintegernoThe pattern setup the ticket was pre-filled from.
Response example
{
  "code": 0,
  "data": {
    "id": 12, "symbol": "BTC-USD-KO", "side": "LONG", "qty": "1", "entry": "65400.0", "barrier": "64700.0",
    "takeProfit": "66800.0", "strict": false, "expiryTs": null, "premium": "630.000000", "premiumBps": 9000,
    "potentialProfit": "1400.000000", "openedAt": 1755855750000, "state": "OPEN"
  }
}
Errors
CodeHTTPWhen
4000400The 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."
4000400The 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).
4000400A 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.
4004404Not an offered market.
4010401No or unknown client identity.
5020502The venue's instrument specs were unreachable.
5030503The 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.

Request body
NameTypeRequiredDescription
confirmedbooleanyesMust be true: the box is ticked.
Response example
{
  "code": 0,
  "data": { "wallet": { "balance": "10000.00", "currency": "USDC", "alias": "Trader-3F2A", "resets": 0, "startingBalance": "10000.00", "ageConfirmedAt": 1757500800000 } }
}
Errors
CodeHTTPWhen
4000400The body is missing or confirmed is not true: "Tick the box to confirm that you are 18 or older."
4010401No or unknown client identity.
5030503The 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.

Request body
NameTypeRequiredDescription
demoIdintegeryesThe demo contract id.
Response example
{
  "code": 0,
  "data": { "id": 12, "state": "CLOSED", "closeReason": "earlyClose", "settlePrice": "65500.0", "pnl": "90.000000" }
}
Errors
CodeHTTPWhen
4000400A stale mark, or the contract is not open.
4004404Not an own demo contract.
4010401No or unknown client identity.
5030503The demo account is switched off on this server
POST/v1/demo/resetSession only

Reset the demo wallet

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.

Response example
{
  "code": 0,
  "data": { "wallet": { "balance": "10000.00", "currency": "USDC", "alias": "Trader-3F2A", "resets": 1, "startingBalance": "10000.00", "ageConfirmedAt": 1757500800000 } }
}
Errors
CodeHTTPWhen
4000400A demo contract is still open.
4010401No or unknown client identity.
5030503The demo account is switched off on this server
POST/v1/demo/aliasSession only

Rename the league alias

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.

Request body
NameTypeRequiredDescription
aliasstringyesThe new alias.
Response example
{
  "code": 0,
  "data": { "wallet": { "balance": "10000.00", "currency": "USDC", "alias": "Luna", "resets": 0, "startingBalance": "10000.00", "ageConfirmedAt": 1757500800000 } }
}
Errors
CodeHTTPWhen
4000400An alias outside the 3 to 16 character rule.
4010401No or unknown client identity.
5030503The demo account is switched off on this server
GET/v1/demo/leagueKey: READ

The weekly demo league

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
NameInTypeRequiredDescription
weekquerystringnoAn ISO week like "2026-W35"; omitted = the current week.
Response example
{
  "code": 0,
  "data": {
    "week": "2026-W35",
    "minContracts": 3,
    "rows": [
      { "rank": 1, "alias": "Luna", "contracts": 5, "premium": "3150.000000", "pnl": "4200.000000", "score": "1.3333", "you": false }
    ]
  }
}
Errors
CodeHTTPWhen
4000400A week that does not look like 2026-W35.
4010401No or unknown client identity.
5030503The demo account is switched off on this server
4290429The 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 "https://barriers.dev.mtf.perpetuals.com:9800/v1/demo/league?week=2026-W35" -H "X-Api-Key: bak_EXAMPLE"

Community and referrals

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
NameTypeRequiredDescription
symbolstringyesA BARRIER market, e.g. BTC-USD-KO.
sidestringyesLONG, SHORT, BUY or SELL (stored as LONG or SHORT).
barrierstringyesThe knock-out level as a decimal string.
takeProfitstringnoThe take profit level; omitted for a knock-out-only setup.
strictbooleanyesThe strict take profit flag of the setup.
expiryKeystringyes30m, 1h, 4h, 8h or none.
qtystringnoThe size the setup was drafted at; omitted when the ticket was sized by premium.
notestringnoA short note shown on the community card; at most 140 characters, control characters stripped.
drawingsarraynoThe 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.
showImagebooleannoShow 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.".
showPerformancebooleannoPublish 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).
Response example
{
  "code": 0,
  "data": {
    "setup": {
      "id": 12,
      "token": "s7Kq2mVxw1",
      "symbol": "BTC-USD-KO",
      "side": "LONG",
      "barrier": "63500.0",
      "takeProfit": "66200.0",
      "strict": false,
      "expiryKey": "1h",
      "qty": "0.5",
      "note": "Range floor held twice today.",
      "alias": "Luna",
      "createdAt": 1756200000000,
      "expiresAt": 1756457400000,
      "mine": true,
      "drawings": [
        { "type": "trendline", "points": [{ "t": 1756114800000, "p": "63200.0" }, { "t": 1756200000000, "p": "63900.0" }] },
        { "type": "hline", "points": [{ "t": 0, "p": "64500.0" }] }
      ],
      "image": { "url": "/v1/share/s7Kq2mVxw1/image", "v": "3f9a1c0b7e2d" },
      "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."
      },
      "visibility": { "showImage": true, "showPerformance": true }
    }
  }
}
Errors
CodeHTTPWhen
4000400Validation 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.
4010401No or unknown client identity.
curl
curl -X POST https://barriers.dev.mtf.perpetuals.com:9800/v1/community/setups -H "Authorization: Bearer SESSION" -H "Content-Type: application/json" -d '{"symbol":"BTC-USD-KO","side":"LONG","barrier":"63500.0","takeProfit":"66200.0","strict":false,"expiryKey":"1h","qty":"0.5"}'
GET/v1/community/setupsKey: READ

The live shared setups

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.

Response example
{
  "code": 0,
  "data": {
    "disclosure": "Shared setups are other clients' own configurations, not recommendations or advice. Prices move; check every figure before trading.",
    "asOf": 1756201000000,
    "setups": [
      {
        "id": 12,
        "token": "s7Kq2mVxw1",
        "symbol": "BTC-USD-KO",
        "side": "LONG",
        "barrier": "63500.0",
        "takeProfit": "66200.0",
        "strict": false,
        "expiryKey": "1h",
        "qty": "0.5",
        "note": "Range floor held twice today.",
        "alias": "Luna",
        "createdAt": 1756200000000,
        "expiresAt": 1756457400000,
        "mine": false,
        "drawings": [],
        "image": { "url": "/v1/share/s7Kq2mVxw1/image", "v": "3f9a1c0b7e2d" },
        "performance": null,
        "preview": {
          "mark": "64120.5",
          "premiumPerUnit": "558.450000",
          "potentialProfitPerUnit": "2079.500000",
          "leverage": "103.42"
        }
      }
    ]
  }
}
Errors
CodeHTTPWhen
4010401No or unknown client identity.
4290429The 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 https://barriers.dev.mtf.perpetuals.com:9800/v1/community/setups -H "X-Api-Key: bak_EXAMPLE"
DELETE/v1/community/setups/{id}Session only

Remove one of your shared setups

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.

Path and query parameters
NameInTypeRequiredDescription
idpathintegeryesThe shared setup's id.
Response example
{
  "code": 0,
  "data": { "removed": true }
}
Errors
CodeHTTPWhen
4004404The setup does not exist or is not the caller's.
4010401No or unknown client identity.
curl
curl -X DELETE https://barriers.dev.mtf.perpetuals.com:9800/v1/community/setups/12 -H "Authorization: Bearer SESSION"
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
NameInTypeRequiredDescription
idpathintegeryesThe shared setup's id.
Request body
NameTypeRequiredDescription
showImagebooleannoShow your profile image on this share.
showPerformancebooleannoPublish (a fresh snapshot) or withdraw (drop the snapshot) your barrier results on this share.
Response example
{
  "code": 0,
  "data": {
    "setup": {
      "id": 12,
      "token": "s7Kq2mVxw1",
      "symbol": "BTC-USD-KO",
      "side": "LONG",
      "barrier": "63500.0",
      "takeProfit": "66200.0",
      "strict": false,
      "expiryKey": "1h",
      "qty": "0.5",
      "note": "Range floor held twice today.",
      "alias": "Luna",
      "createdAt": 1756200000000,
      "expiresAt": 1756457400000,
      "mine": true,
      "drawings": [],
      "image": null,
      "performance": null,
      "visibility": { "showImage": false, "showPerformance": false }
    }
  }
}
Errors
CodeHTTPWhen
4000400Neither member, a non-boolean value ("Send showImage or showPerformance."), showImage true without an image, or showPerformance true below the minimum sample.
4004404The setup is not yours, was removed or expired.
4010401No or unknown client identity.
curl
curl -X POST https://barriers.dev.mtf.perpetuals.com:9800/v1/community/setups/12/visibility -H "Authorization: Bearer SESSION" -H "Content-Type: application/json" -d '{"showPerformance":false}'
GET/v1/community/profileSession only

Your community profile

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."
  }
}
Errors
CodeHTTPWhen
4010401No or unknown client identity.
4034403Called with an API key (a session surface).
curl
curl https://barriers.dev.mtf.perpetuals.com:9800/v1/community/profile -H "Authorization: Bearer SESSION"
POST/v1/community/profile/imageSession only

Set your profile image

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
NameTypeRequiredDescription
filefileyesThe image (multipart part named file): JPEG or PNG, up to 8 MB.
Response example
{
  "code": 0,
  "data": {
    "image": { "url": "/v1/community/profile/image", "v": "3f9a1c0b7e2d", "sizeBytes": 48211, "width": 512, "height": 512, "updatedAt": 1756900000000 }
  }
}
Errors
CodeHTTPWhen
4000400Not 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.
4010401No or unknown client identity.
4039403Profile image uploads are blocked on this account.
4290429More 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.

Response example
<the JPEG bytes>

ETag: "<sha256>"
Cache-Control: private, no-cache
Content-Type: image/jpeg
Content-Disposition: inline; filename="profile.jpg"
Errors
CodeHTTPWhen
4004404You have no profile image.
4010401No or unknown client identity.
curl
curl https://barriers.dev.mtf.perpetuals.com:9800/v1/community/profile/image -H "Authorization: Bearer SESSION" -o profile.jpg
DELETE/v1/community/profile/imageSession only

Remove your profile image

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.

Response example
{
  "code": 0,
  "data": { "removed": true }
}
Errors
CodeHTTPWhen
4004404You have no profile image.
4010401No or unknown client identity.
curl
curl -X DELETE https://barriers.dev.mtf.perpetuals.com:9800/v1/community/profile/image -H "Authorization: Bearer SESSION"
PUT/v1/community/profile/defaultsSession only

Your defaults for new shares

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
NameTypeRequiredDescription
showImagebooleannoPre-set "show my profile image" on new shares.
showPerformancebooleannoPre-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
CodeHTTPWhen
4000400Neither member, or a non-boolean value: "Send showImage or showPerformance."
4010401No or unknown client identity.
curl
curl -X PUT https://barriers.dev.mtf.perpetuals.com:9800/v1/community/profile/defaults -H "Authorization: Bearer SESSION" -H "Content-Type: application/json" -d '{"showPerformance":true}'
GET/v1/referralKey: READ

Your referral code and tally

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
CodeHTTPWhen
4010401No or unknown client identity.
4290429The 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 https://barriers.dev.mtf.perpetuals.com:9800/v1/referral -H "X-Api-Key: bak_EXAMPLE"
GET/v1/share/{token}Public

The public view of a shared setup

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.

Path and query parameters
NameInTypeRequiredDescription
tokenpathstringyesThe share token from the setup's link.
Response example
{
  "code": 0,
  "data": {
    "setup": {
      "id": 12,
      "token": "s7Kq2mVxw1",
      "symbol": "BTC-USD-KO",
      "side": "LONG",
      "barrier": "63500.0",
      "takeProfit": "66200.0",
      "strict": false,
      "expiryKey": "1h",
      "qty": "0.5",
      "note": "Range floor held twice today.",
      "alias": "Luna",
      "createdAt": 1756200000000,
      "expiresAt": 1756457400000,
      "drawings": [
        { "type": "trendline", "points": [{ "t": 1756114800000, "p": "63200.0" }, { "t": 1756200000000, "p": "63900.0" }] },
        { "type": "hline", "points": [{ "t": 0, "p": "64500.0" }] }
      ],
      "image": { "url": "/v1/share/s7Kq2mVxw1/image", "v": "3f9a1c0b7e2d", "onUnfurl": 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."
      }
    },
    "mark": "64120.5",
    "candles": [
      { "t": 1756114800000, "open": "63980.0", "high": "64150.0", "low": "63890.5", "close": "64090.0" }
    ],
    "candlesAsOf": 1756201000000,
    "disclosure": "Shared setups are other clients' own configurations, not recommendations or advice. Prices move; check every figure before trading."
  }
}
Errors
CodeHTTPWhen
4004404Unknown, removed or expired token.
curl
curl https://barriers.dev.mtf.perpetuals.com:9800/v1/share/s7Kq2mVxw1
GET/v1/share/{token}/imagePublic

The creator's profile image of a shared setup

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
NameInTypeRequiredDescription
tokenpathstringyesThe share token from the setup's link.
vquerystringnoA cache buster (the setup row's image.v); ignored by the router.
Response example
<the JPEG bytes>

ETag: "<sha256>"
Cache-Control: private, max-age=300
Content-Type: image/jpeg
Content-Disposition: inline; filename="profile.jpg"
Referrer-Policy: no-referrer
Errors
CodeHTTPWhen
4004404Unknown, removed or expired token; the share hides the image; no image; a closed account (one uniform answer).
4290429More than 300 requests within a minute from one address.
curl
curl "https://barriers.dev.mtf.perpetuals.com:9800/v1/share/s7Kq2mVxw1/image?v=3f9a1c0b7e2d" -o creator.jpg

Account

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).

Response example
{
  "clientId": 12,
  "clientUid": "BARR-00000012",
  "email": "client@example.com",
  "emailVerified": true,
  "mfaEnabled": true,
  "displayName": "Alex Trader",
  "clientType": "NATURAL",
  "categorization": "RETAIL",
  "lifecycleState": "ACTIVE",
  "verificationLevel": "MICA_MIFID",
  "country": "DE",
  "createdAt": 1786800000000,
  "openCases": 1,
  "unreadCaseMessages": 0
}
Errors
CodeHTTPWhen
4010401No, unknown or expired credential ("Sign in again.")
4290429The 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 https://barriers.dev.mtf.perpetuals.com:9800/v1/me -H "X-Api-Key: bak_EXAMPLE"
GET/v1/account/overviewKey: READ

The account's headline figures in one round trip

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).

Response example
{
  "code": 0,
  "data": {
    "currency": "USDC",
    "equity": "10250.410000",
    "cash": "9800.000000",
    "freeMargin": "9100.250000",
    "initialMargin": "1150.160000",
    "unrealizedPnl": "450.410000",
    "barrierMargin": "0.000000",
    "barrierUnrealizedPnl": "0.000000",
    "marksFresh": true,
    "reserved": "125.000000",
    "holdings": [
      {
        "currency": "BTC", "kind": "ASSET", "displayDecimals": 8,
        "total": "0.05000000", "reserved": "0.00000000",
        "staked": "0.00000000", "claims": "0.00000000",
        "mark": "65000", "markFresh": true, "value": "3250.000000",
        "stakedValue": null, "claimsValue": null, "valueCurrency": "USDC", "valuationBasis": "MARK",
        "avgCost": "61000", "costBasis": "3050.000000", "costQty": "0.05",
        "unrealizedPnl": "200.000000", "spotSymbol": "BTC-USD"
      }
    ],
    "staked": [
      { "currency": "USDC", "staked": "1000.000000", "claims": "0.000000", "mark": "1", "markFresh": true, "value": "1000.000000", "claimsValue": "0.000000", "valueCurrency": "USDC", "valuationBasis": "CASH" }
    ],
    "holdingsValue": "3250.000000",
    "stakedValue": "1000.000000",
    "claimsValue": "0.000000",
    "holdingsValued": true,
    "valuationBasis": "MARK",
    "portfolioValue": "14500.410000",
    "realizedPnl": "1210.500000",
    "realizedPnl30d": "180.250000",
    "commissionPaid": "42.100000",
    "fundingPaid": "12.400000",
    "realizedTotal": "1395.750000",
    "realizedByFamily": { "perp": "1210.500000", "spot": "85.250000", "barrier": "90.000000", "staking": "10.000000" },
    "commissionByFamily": { "perp": "30.000000", "spot": "4.100000", "barrier": "6.000000", "staking": "2.000000" },
    "stakingByAsset": [ { "currency": "ETH", "rewardReceived": "0.01200000", "commission": "0.00120000" } ],
    "tradeCount": 152,
    "closedPositions": 48,
    "wins": 27,
    "losses": 21,
    "counts": { "perpFills": 140, "spotFills": 12, "barrierContracts": 5, "swapTakes": 2, "barrierSettled": 4, "barrierWins": 2, "barrierLosses": 2 },
    "allocation": {
      "cash": "9800.000000",
      "reserved": "125.000000",
      "positions": [ { "symbol": "BTC-USD-PERP", "family": "PERP", "side": "LONG", "notional": "6500.000000", "upnl": "450.410000", "markFresh": true } ],
      "barriers": [],
      "holdings": [ { "currency": "BTC", "notional": "3250.000000", "markFresh": true, "upnl": "200.000000" } ],
      "staked": [ { "currency": "USDC", "notional": "1000.000000", "markFresh": true } ]
    },
    "verificationLevel": "MICA_MIFID",
    "openCases": 1,
    "unreadCaseMessages": 0
  }
}
Errors
CodeHTTPWhen
4010401Unknown client
5030503The margin engine is not attached
4290429The 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 https://barriers.dev.mtf.perpetuals.com:9800/v1/account/overview -H "X-Api-Key: bak_EXAMPLE"
GET/v1/balancesKey: READ

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.

Response example
{
  "balances": [
    {
      "currency": "USDC", "kind": "CASH",
      "cash": "9800.000000", "reserved": "125.000000", "total": "9925.000000", "free": "9800.000000",
      "displayDecimals": 2, "venueAsset": "USD",
      "equity": "10250.410000", "unrealizedPnl": "450.410000", "freeMargin": "9100.250000",
      "mark": null, "markFresh": null, "value": "9925.000000", "valueCurrency": "USDC",
      "staked": "1000.000000", "swapClaims": "0.000000",
      "stakedValue": "1000.000000", "claimsValue": "0.000000", "valuationBasis": "CASH"
    },
    {
      "currency": "BTC", "kind": "ASSET",
      "cash": "0.05000000", "reserved": "0.00000000", "total": "0.05000000", "free": "0.05000000",
      "displayDecimals": 8, "venueAsset": "BTC",
      "equity": null, "unrealizedPnl": null, "freeMargin": null,
      "mark": "65000", "markFresh": true, "value": "3250.000000", "valueCurrency": "USDC",
      "staked": "0.00000000", "swapClaims": "0.00000000",
      "stakedValue": null, "claimsValue": null, "valuationBasis": "MARK"
    }
  ],
  "portfolioValue": "14500.410000",
  "portfolioCurrency": "USDC",
  "portfolio": {
    "cashTotal": "9925.000000", "reserved": "125.000000", "withdrawalHolds": "0.000000", "holdingsValue": "3250.000000",
    "stakedValue": "1000.000000", "unrealizedPnl": "450.410000", "claimsValue": "0.000000",
    "holdingsValued": true, "currency": "USDC", "valuationBasis": "MARK"
  }
}
Errors
CodeHTTPWhen
4010401No or unknown credential
4290429The 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 https://barriers.dev.mtf.perpetuals.com:9800/v1/balances -H "X-Api-Key: bak_EXAMPLE"
GET/v1/account/equity-historyKey: READ

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
NameInTypeRequiredDescription
rangequerystringnoOne 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."
Response example
{
  "code": 0,
  "data": {
    "range": "7d",
    "valuationVersion": 1,
    "points": [
      { "ts": 1786800000000, "equity": "10100.000000", "cash": "9700.000000", "upnl": "400.000000", "holdingsValue": "3180.000000", "staked": "1000.000000", "portfolioValue": "14280.000000", "valuationVersion": 1 }
    ],
    "live": { "ts": 1787356800000, "equity": "10250.410000", "cash": "9800.000000", "upnl": "450.410000", "holdingsValue": "3250.000000", "staked": "1000.000000", "portfolioValue": "14500.410000", "valuationVersion": 1 },
    "liveMarksFresh": true,
    "liveHoldingsValued": true,
    "changeAbs": "150.410000",
    "changePct": "1.49",
    "portfolioChangeAbs": "220.410000",
    "portfolioChangePct": "1.54",
    "currency": "USDC"
  }
}
Errors
CodeHTTPWhen
4000400Unknown range
4010401Unknown client
5030503The margin engine is not attached
4290429The 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 "https://barriers.dev.mtf.perpetuals.com:9800/v1/account/equity-history?range=30d" -H "X-Api-Key: bak_EXAMPLE"
GET/v1/account/summaryKey: READ

Lifetime trading aggregates

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}).

Response example
{
  "code": 0,
  "data": {
    "currency": "USDC",
    "realizedPnl": "142.51",
    "commissionPaid": "18.06",
    "fundingPaid": "3.42",
    "tradeCount": 87,
    "since": "2026-08-15T09:12:44.120Z",
    "sinceTs": 1786871564120,
    "realizedTotal": "186.13",
    "realizedByFamily": {
      "fills": "142.51",
      "spot": "12.40",
      "barrier": "28.10",
      "staking": "3.12"
    },
    "commissionByFamily": {
      "fills": "14.20",
      "barrier": "3.11",
      "staking": "0.75"
    },
    "counts": {
      "fills": 87,
      "barrierContracts": 26,
      "swapTakes": 5
    }
  }
}
Errors
CodeHTTPWhen
4010401No or unknown credentials.
4290429The 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 -H 'X-Api-Key: bak_EXAMPLE' https://barriers.dev.mtf.perpetuals.com:9800/v1/account/summary
GET/v1/consentsSession only

The consent status per document

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.

Response example
{
  "reconsentRequired": false,
  "documents": [
    {
      "doc": "TERMS",
      "title": "Terms and Conditions",
      "url": "https://barriers.dev.mtf.perpetuals.com/legal/terms-v2.pdf",
      "currentVersion": "2.0",
      "effectiveAt": 1786000000000,
      "acceptedVersion": "2.0",
      "acceptedAt": 1786100000000,
      "acceptedVia": "PORTAL",
      "reconsentRequired": false,
      "gateFrom": null
    }
  ]
}
Errors
CodeHTTPWhen
4010401No session
4034403Called with an API key (excluded namespace)
curl
curl https://barriers.dev.mtf.perpetuals.com:9800/v1/consents -H "Authorization: Bearer <session-token>"
POST/v1/consents/acceptSession only

Accept the current version of a 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
NameTypeRequiredDescription
docstringyesThe document code (e.g. TERMS, PRIVACY)
versionstringyesMust be the CURRENT version of the document; a superseded or not yet effective version is refused
Response example
{
  "reconsentRequired": false,
  "documents": [
    {
      "doc": "TERMS",
      "title": "Terms and Conditions",
      "url": "https://barriers.dev.mtf.perpetuals.com/legal/terms-v2.pdf",
      "currentVersion": "2.0",
      "effectiveAt": 1786000000000,
      "acceptedVersion": "2.0",
      "acceptedAt": 1787356800000,
      "acceptedVia": "PORTAL",
      "reconsentRequired": false,
      "gateFrom": null
    }
  ]
}
Errors
CodeHTTPWhen
4000400doc and version are required
4004404No version of the document is in force
4092409The named version is not the current one (the answer carries currentVersion)
curl
curl -X POST https://barriers.dev.mtf.perpetuals.com:9800/v1/consents/accept -H "Authorization: Bearer <session-token>" -H "Content-Type: application/json" -d "{\"doc\": \"TERMS\", \"version\": \"2.0\"}"
GET/v1/account/closureSession only

The closure request status

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.

Response example
{
  "requested": false,
  "requestedAt": null,
  "closed": false
}
Errors
CodeHTTPWhen
4010401No session
curl
curl https://barriers.dev.mtf.perpetuals.com:9800/v1/account/closure -H "Authorization: Bearer <session-token>"
POST/v1/account/closureSession only

Request the closure of the account

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
NameTypeRequiredDescription
reasonstringnoAn 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
CodeHTTPWhen
4010401No session
4092409The account is already closed ("Your account is already closed.")
curl
curl -X POST https://barriers.dev.mtf.perpetuals.com:9800/v1/account/closure -H "Authorization: Bearer <session-token>" -H "Content-Type: application/json" -d "{\"reason\": \"No longer trading\"}"
GET/v1/achievementsKey: READ

Skill badges and unlocks

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.

Response example
{
  "code": 0,
  "data": {
    "badges": [
      { "code": "FIRST_CONTRACT", "name": "First contract", "description": "You opened your first barrier contract.", "earnedAt": 1755855750000 },
      { "code": "DEMO_GRADUATE", "name": "Demo graduate", "description": "You settled ten demo contracts.", "earnedAt": null }
    ],
    "earned": 1,
    "unlocks": { "patternSymbolStats": false }
  }
}
Errors
CodeHTTPWhen
4010401No or unknown client identity.
4290429The 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 https://barriers.dev.mtf.perpetuals.com:9800/v1/achievements -H "X-Api-Key: bak_EXAMPLE"

Money

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.

Response example
{
  "code": 0,
  "data": {
    "available": true,
    "networks": [
      { "asset": "USDC", "network": "ETH", "requiredConfirmations": 12, "withdrawalFee": "2.000000", "minWithdrawal": "10.000000", "displayDecimals": 2 },
      { "asset": "BTC", "network": "BTC", "requiredConfirmations": 3, "withdrawalFee": "0.00010000", "minWithdrawal": "0.00050000", "displayDecimals": 8 }
    ],
    "allowlistCoolingHours": 24,
    "dailyLimitUsdc": "50000.000000",
    "remainingTodayUsdc": "48000.000000",
    "singleMaxUsdc": "20000.000000",
    "selfHostedProofAboveEur": "1000",
    "eurPerUsdc": "0.92",
    "selfHostedProofAboveUsdc": "1086.956521",
    "mfaStepUp": true,
    "note": "Awaiting approval"
  }
}
Errors
CodeHTTPWhen
4010401No or unknown credential
4290429The 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 https://barriers.dev.mtf.perpetuals.com:9800/v1/money/config -H "X-Api-Key: bak_EXAMPLE"
GET/v1/money/deposit-addressSession

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
NameInTypeRequiredDescription
assetquerystringyesThe asset code (USDC, BTC, ...)
networkquerystringyesThe network code of the pair, as networks[].network of GET /v1/money/config lists it (ETH, not Ethereum)
Response example
{
  "code": 0,
  "data": {
    "asset": "USDC",
    "network": "ETH",
    "address": "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063",
    "memo": null,
    "requiredConfirmations": 12,
    "provisionedAt": 1787356800000
  }
}
Errors
CodeHTTPWhen
4000400Unsupported asset and network pair ("This asset is not supported on that network.")
4030403Below the MiCA level: data carries {action: "DEPOSIT:CRYPTO", level, requiredLevel, missingBlocks}
4000403A deposit restriction stands on the account
4034403Called with an API key (the GET provisions at custody)
5030503Custody is not configured or unreachable
curl
curl "https://barriers.dev.mtf.perpetuals.com:9800/v1/money/deposit-address?asset=USDC&network=ETH" -H "Authorization: Bearer <session-token>"
GET/v1/money/allowlistKey: READ

The saved withdrawal addresses

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).

Response example
{
  "code": 0,
  "data": {
    "allowlist": [
      {
        "allowlistId": 3,
        "asset": "USDC",
        "network": "ETH",
        "address": "0x1A2b3C4d5E6f7A8b9C0d1E2f3A4b5C6d7E8f9A0b",
        "memo": null,
        "label": "My hardware wallet",
        "walletType": "SELF_HOSTED",
        "beneficiaryName": null,
        "vaspName": null,
        "vaspLei": null,
        "beneficiaryCountry": null,
        "ownershipProof": "Signed message of 2026-08-20, kept on file",
        "proof": { "kind": "SIGNED_MESSAGE", "status": "VERIFIED", "verifiedAt": 1787280000000 },
        "addedAt": 1787270400000,
        "activatesAt": 1787356800000,
        "active": true,
        "removedAt": null,
        "removedBy": null,
        "removeReason": null
      }
    ],
    "coolingHours": 24
  }
}
Errors
CodeHTTPWhen
4010401No or unknown credential
4290429The 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 https://barriers.dev.mtf.perpetuals.com:9800/v1/money/allowlist -H "X-Api-Key: bak_EXAMPLE"
POST/v1/money/allowlistSession only

Save a withdrawal address

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
NameTypeRequiredDescription
assetstringyesThe asset code of the pair
networkstringyesThe network code of the pair, as networks[].network of GET /v1/money/config lists it (ETH, not Ethereum)
addressstringyes8 to 128 characters, no whitespace
memostringnoDestination memo or tag where the network uses one
labelstringyesA short label, 1 to 60 characters
walletTypestringyesSELF_HOSTED or VASP (an account at an exchange or custodian)
beneficiaryNamestringnoRequired for a VASP entry: the account holder at the exchange or custodian
vaspNamestringnoThe exchange or custodian's name
vaspLeistringnoThe exchange or custodian's LEI
beneficiaryCountrystringnoRequired 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
ownershipProofstringnoAn 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))
codestringnoThe six-digit authenticator code; required on a two-factor account (the withdrawal-shaped step-up)
Response example
{
  "code": 0,
  "data": {
    "allowlistId": 4,
    "asset": "USDC",
    "network": "ETH",
    "address": "0x9B8c7D6e5F4a3B2c1D0e9F8a7B6c5D4e3F2a1B0c",
    "memo": null,
    "label": "Exchange account",
    "walletType": "VASP",
    "beneficiaryName": "Alex Trader",
    "vaspName": "Example Exchange Ltd",
    "vaspLei": null,
    "beneficiaryCountry": "DE",
    "ownershipProof": null,
    "proof": { "kind": "NONE", "status": "NONE", "verifiedAt": null },
    "addedAt": 1787356800000,
    "activatesAt": 1787443200000,
    "active": false,
    "removedAt": null,
    "removedBy": null,
    "removeReason": null
  }
}
Errors
CodeHTTPWhen
4000400The pair, the address shape ("Enter a valid address."), the label, the wallet type, or a VASP entry without the beneficiary name or country
4000409The address is already in the list ("This address is already in your list.")
4033400A two-factor account sent no code (data.mfaRequired)
4290429Five wrong codes inside 15 minutes locked the step-up (data.lockedUntil; data.signedOut at the lock: every session is signed out)
4000403A withdrawal restriction stands on the account (FROZEN, NO_WITHDRAWALS)
5030503The money desk is not available on this server
curl
curl -X POST https://barriers.dev.mtf.perpetuals.com:9800/v1/money/allowlist -H "Authorization: Bearer <session-token>" -H "Content-Type: application/json" -d "{\"asset\": \"USDC\", \"network\": \"ETH\", \"address\": \"0x9B8c...1B0c\", \"label\": \"Exchange account\", \"walletType\": \"VASP\", \"beneficiaryName\": \"Alex Trader\", \"beneficiaryCountry\": \"DE\", \"code\": \"123456\"}"
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
NameInTypeRequiredDescription
allowlistIdpathnumberyesThe client's own SELF_HOSTED entry whose evidence is NONE or REJECTED
Response example
{
  "code": 0,
  "data": {
    "allowlistId": 3,
    "challenge": "barriers ownership proof\nclient: BARR-00000042\naddress: 0x1A2b3C4d5E6f7A8b9C0d1E2f3A4b5C6d7E8f9A0b\nnonce: 4f1c9d2a7b3e8f60a1b2c3d4e5f60718\ndate: 2026-09-10T09:00:00.000Z",
    "expiresAt": 1787443200000
  }
}
Errors
CodeHTTPWhen
4004404Unknown, removed or another client's entry
4000400A VASP entry ("A VASP address needs no ownership evidence."), or an entry already verified ("This address is already verified.")
4000409Evidence for this address is already under review
4000403A withdrawal restriction stands on the account (FROZEN, NO_WITHDRAWALS)
4038403The account is serviced by a partner
5030503The 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
NameInTypeRequiredDescription
allowlistIdpathnumberyesThe client's own SELF_HOSTED entry whose evidence is NONE or REJECTED
Request body
NameTypeRequiredDescription
kindstringyesSIGNED_MESSAGE, MICRO_DEPOSIT or ATTESTATION
signaturestringnoSIGNED_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)
publicKeystringnoSIGNED_MESSAGE on XRP: the 33-byte public key of the address in hex (required there, ignored elsewhere)
txHashstringnoMICRO_DEPOSIT: the transaction hash of a small transfer FROM the address to the client's own deposit address, 8 to 128 characters
textstringnoATTESTATION: how the client controls the address, 10 to 500 characters
codestringnoThe six-digit authenticator code; required on a two-factor account, evaluated after the checks so a refused submission never consumes a code
Response example
{
  "code": 0,
  "data": {
    "allowlistId": 3,
    "asset": "USDC",
    "network": "ETH",
    "address": "0x1A2b3C4d5E6f7A8b9C0d1E2f3A4b5C6d7E8f9A0b",
    "memo": null,
    "label": "My hardware wallet",
    "walletType": "SELF_HOSTED",
    "beneficiaryName": null,
    "vaspName": null,
    "vaspLei": null,
    "beneficiaryCountry": null,
    "ownershipProof": null,
    "proof": { "kind": "SIGNED_MESSAGE", "status": "VERIFIED", "verifiedAt": 1787360400000 },
    "addedAt": 1787270400000,
    "activatesAt": 1787356800000,
    "active": true,
    "removedAt": null,
    "removedBy": null,
    "removeReason": null
  }
}
Errors
CodeHTTPWhen
4004404Unknown, removed or another client's entry
4000400A 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.")
4000409Evidence for this address is already under review
4033400A two-factor account sent no code (data.mfaRequired)
4290429Five wrong codes inside 15 minutes locked the step-up (data.lockedUntil; data.signedOut at the lock: every session is signed out)
4000403A withdrawal restriction stands on the account (FROZEN, NO_WITHDRAWALS)
4038403The account is serviced by a partner
5030503The money desk is not available on this server
curl
curl -X POST https://barriers.dev.mtf.perpetuals.com:9800/v1/money/allowlist/3/evidence -H "Authorization: Bearer <session-token>" -H "Content-Type: application/json" -d "{\"kind\": \"SIGNED_MESSAGE\", \"signature\": \"0x5c3a...1b\", \"code\": \"123456\"}"
DELETE/v1/money/allowlist/{allowlistId}Session only

Remove a saved withdrawal address

Removes the client's own entry. A money mutation, excluded for API keys (403 code 4034).

Path and query parameters
NameInTypeRequiredDescription
allowlistIdpathnumberyesThe entry to remove
Response example
{
  "code": 0,
  "data": { "allowlistId": 4, "removed": true }
}
Errors
CodeHTTPWhen
4004404Unknown, foreign or already removed entry ("Choose one of your saved withdrawal addresses.")
curl
curl -X DELETE https://barriers.dev.mtf.perpetuals.com:9800/v1/money/allowlist/4 -H "Authorization: Bearer <session-token>"
POST/v1/money/withdrawalsSession only

Request a withdrawal (TOTP step-up)

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
NameTypeRequiredDescription
assetstringyesThe asset code
networkstringyesThe network code of the pair, as networks[].network of GET /v1/money/config lists it (ETH, not Ethereum)
allowlistIdnumberyesAn ACTIVE entry of the client's own allowlist for the same asset and network
amountstringyesDecimal string in the asset; at least the per-asset minimum
codestringnoThe six-digit authenticator code; required on a two-factor account
Response example
{
  "code": 0,
  "data": {
    "movementId": 41,
    "direction": "OUT",
    "asset": "USDC",
    "network": "ETH",
    "amount": "250.000000",
    "fee": "2.000000",
    "total": "252.000000",
    "address": "0x1A2b3C4d5E6f7A8b9C0d1E2f3A4b5C6d7E8f9A0b",
    "memo": null,
    "allowlistId": 3,
    "state": "REQUESTED",
    "txHash": null,
    "confirmations": 0,
    "requiredConfirmations": 12,
    "submittedAt": null,
    "confirmedAt": null,
    "failedAt": null,
    "cancelledAt": null,
    "cancelledByYou": false,
    "createdAt": 1787356800000,
    "updatedAt": 1787356800000,
    "cancellable": true,
    "statusText": "Awaiting approval"
  }
}
Errors
CodeHTTPWhen
4000400The 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)
4030403The WITHDRAW:CRYPTO verification gate
4000403A withdrawal restriction (FROZEN, NO_WITHDRAWALS)
4000409The reconciliation hold, or "Withdrawals are on hold while your account is in margin call or close-out."
4033400A two-factor account sent no code (data.mfaRequired)
4290429The step-up wrong-code lock (data.lockedUntil; data.signedOut at the lock: every session is signed out)
5030503The asset's value cannot be determined, custody or the margin service is unavailable
curl
curl -X POST https://barriers.dev.mtf.perpetuals.com:9800/v1/money/withdrawals -H "Authorization: Bearer <session-token>" -H "Content-Type: application/json" -d "{\"asset\": \"USDC\", \"network\": \"ETH\", \"allowlistId\": 3, \"amount\": \"250\", \"code\": \"123456\"}"
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).

Path and query parameters
NameInTypeRequiredDescription
movementIdpathnumberyesThe client's own OUT movement
Response example
{
  "code": 0,
  "data": {
    "movementId": 41,
    "direction": "OUT",
    "asset": "USDC",
    "network": "ETH",
    "amount": "250.000000",
    "fee": "2.000000",
    "total": "252.000000",
    "address": "0x1A2b3C4d5E6f7A8b9C0d1E2f3A4b5C6d7E8f9A0b",
    "memo": null,
    "allowlistId": 3,
    "state": "CANCELLED",
    "txHash": null,
    "confirmations": 0,
    "requiredConfirmations": 12,
    "submittedAt": null,
    "confirmedAt": null,
    "failedAt": null,
    "cancelledAt": 1787357100000,
    "cancelledByYou": true,
    "createdAt": 1787356800000,
    "updatedAt": 1787357100000,
    "cancellable": false,
    "statusText": "Cancelled"
  }
}
Errors
CodeHTTPWhen
4004404Unknown or another client's movement ("This movement was not found.")
4000409Not 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
NameInTypeRequiredDescription
directionquerystringnoIN or OUT; absent = both
limitquerynumberno1 to 200, default 50
beforequerynumbernoKeyset cursor: the nextBefore of the previous page (a movement id)
Response example
{
  "code": 0,
  "data": {
    "movements": [
      {
        "movementId": 40,
        "direction": "IN",
        "asset": "USDC",
        "network": "ETH",
        "amount": "500.000000",
        "fee": "0.000000",
        "total": "500.000000",
        "address": "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063",
        "memo": null,
        "allowlistId": null,
        "state": "CONFIRMED",
        "txHash": "0xabc123def4567890abc123def4567890abc123def4567890abc123def4567890",
        "confirmations": 12,
        "requiredConfirmations": 12,
        "submittedAt": null,
        "confirmedAt": 1787300000000,
        "failedAt": null,
        "cancelledAt": null,
        "cancelledByYou": false,
        "createdAt": 1787290000000,
        "updatedAt": 1787300000000,
        "cancellable": false,
        "statusText": "Credited",
        "costBasis": null,
        "costBasisDeclarable": false
      }
    ],
    "hasMore": false,
    "nextBefore": null
  }
}
Errors
CodeHTTPWhen
4000400The page size, the cursor, or a direction that is not IN or OUT
4290429The 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 "https://barriers.dev.mtf.perpetuals.com:9800/v1/money/movements?direction=IN&limit=50" -H "X-Api-Key: bak_EXAMPLE"
GET/v1/money/movements/{movementId}Key: READ

One movement

The client view of one movement; 404 for another client's id.

Path and query parameters
NameInTypeRequiredDescription
movementIdpathnumberyesThe client's own movement
Response example
{
  "code": 0,
  "data": {
    "movementId": 40,
    "direction": "IN",
    "asset": "USDC",
    "network": "ETH",
    "amount": "500.000000",
    "fee": "0.000000",
    "total": "500.000000",
    "address": "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063",
    "memo": null,
    "allowlistId": null,
    "state": "CONFIRMED",
    "txHash": "0xabc123def4567890abc123def4567890abc123def4567890abc123def4567890",
    "confirmations": 12,
    "requiredConfirmations": 12,
    "submittedAt": null,
    "confirmedAt": 1787300000000,
    "failedAt": null,
    "cancelledAt": null,
    "cancelledByYou": false,
    "createdAt": 1787290000000,
    "updatedAt": 1787300000000,
    "cancellable": false,
    "statusText": "Credited",
    "costBasis": null,
    "costBasisDeclarable": false
  }
}
Errors
CodeHTTPWhen
4004404Unknown or another client's movement
4290429The 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 https://barriers.dev.mtf.perpetuals.com:9800/v1/money/movements/40 -H "X-Api-Key: bak_EXAMPLE"
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
NameInTypeRequiredDescription
movementIdpathnumberyesThe client's own CREDITED crypto deposit of an asset (never a USDC deposit, never a withdrawal)
Request body
NameTypeRequiredDescription
costUsdcstringyesThe 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)
Response example
{
  "code": 0,
  "data": {
    "costBasis": {
      "source": "DECLARED",
      "costUsdc": "12345.500000",
      "markUsdc": null,
      "declaredAt": 1787300500000
    }
  }
}
Errors
CodeHTTPWhen
4004404Unknown or another client's movement ("This movement was not found.")
4000400Not 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.")
curl
curl -X PUT https://barriers.dev.mtf.perpetuals.com:9800/v1/money/movements/40/cost-basis -H "Authorization: Bearer <session-token>" -H "Content-Type: application/json" -d "{\"costUsdc\": \"12345.5\"}"

Statements

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
CodeHTTPWhen
4010401No or unknown credential
4290429The 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 https://barriers.dev.mtf.perpetuals.com:9800/v1/statements/costs -H "X-Api-Key: bak_EXAMPLE"
GET/v1/statementsKey: READ

The client's statements, newest first

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
NameInTypeRequiredDescription
kindquerystringnoACCOUNT, COSTS, TRANSACTIONS, TAX or CLIENT_ASSETS; absent = every kind
limitquerynumberno1 to 200, default 50
Response example
{
  "statements": [
    {
      "id": 12,
      "clientId": 12,
      "kind": "ACCOUNT",
      "periodFrom": "2026-07-01",
      "periodTo": "2026-07-31",
      "basis": "LAST_MONTH",
      "generatedAt": 1787356800000,
      "generatedBy": "CLIENT",
      "title": "Account statement 2026-07-01 to 2026-07-31",
      "formats": ["pdf", "csv"],
      "pdfBytes": 48213,
      "sha256": "9d2f0f6f0a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5",
      "version": 1,
      "supersedes": null,
      "supersededBy": null
    }
  ]
}
Errors
CodeHTTPWhen
4000400An unknown kind (reason STATEMENT_KIND) or a bad limit
4290429The 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 "https://barriers.dev.mtf.perpetuals.com:9800/v1/statements?kind=ACCOUNT&limit=50" -H "X-Api-Key: bak_EXAMPLE"
POST/v1/statementsSession only

Generate a statement

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
NameTypeRequiredDescription
kindstringyesACCOUNT, COSTS, TRANSACTIONS, TAX or CLIENT_ASSETS
presetstringnoLAST_30_DAYS, LAST_MONTH, THIS_YEAR, LAST_YEAR, CUSTOM, SINCE_LAST_STATEMENT, LAST_QUARTER or THIS_QUARTER
fromstringnoYYYY-MM-DD, with preset CUSTOM (at most 24 months)
tostringnoYYYY-MM-DD, with preset CUSTOM
yearnumbernoThe tax statement's year (up to 5 years back)
Response example
{
  "id": 13,
  "clientId": 12,
  "kind": "TAX",
  "periodFrom": "2025-01-01",
  "periodTo": "2025-12-31",
  "basis": "LAST_YEAR",
  "generatedAt": 1787356900000,
  "generatedBy": "CLIENT",
  "title": "Tax statement 2025",
  "formats": ["pdf", "csv"],
  "pdfBytes": 51200,
  "sha256": "0a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f9",
  "version": 1,
  "supersedes": null,
  "supersededBy": null
}
Errors
CodeHTTPWhen
4000400The kind (STATEMENT_KIND) or the period (STATEMENT_RANGE)
4290429The hourly generation cap (STATEMENT_RATE)
curl
curl -X POST https://barriers.dev.mtf.perpetuals.com:9800/v1/statements -H "Authorization: Bearer <session-token>" -H "Content-Type: application/json" -d "{\"kind\": \"ACCOUNT\", \"preset\": \"LAST_MONTH\"}"
GET/v1/statements/{id}Key: READ

One statement with its model

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.

Path and query parameters
NameInTypeRequiredDescription
idpathnumberyesThe client's own statement id
Response example
{
  "id": 12,
  "clientId": 12,
  "kind": "ACCOUNT",
  "periodFrom": "2026-07-01",
  "periodTo": "2026-07-31",
  "basis": "LAST_MONTH",
  "generatedAt": 1787356800000,
  "generatedBy": "CLIENT",
  "title": "Account statement 2026-07-01 to 2026-07-31",
  "formats": ["pdf", "csv"],
  "pdfBytes": 48213,
  "sha256": "9d2f0f6f0a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5",
  "model": { "kind": "ACCOUNT", "period": { "from": "2026-07-01", "to": "2026-07-31" } }
}
Errors
CodeHTTPWhen
4004404Unknown or another client's id (STATEMENT_NOT_FOUND)
4290429The 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 https://barriers.dev.mtf.perpetuals.com:9800/v1/statements/12 -H "X-Api-Key: bak_EXAMPLE"
GET/v1/statements/{id}/pdfKey: READ

Download the PDF

Streams the PDF as an attachment (nosniff, no-store).

Path and query parameters
NameInTypeRequiredDescription
idpathnumberyesThe client's own statement id
Response example
Binary application/pdf attachment, Content-Disposition: attachment; filename="barriers-account-2026-07-01-2026-07-31-12.pdf"
Errors
CodeHTTPWhen
4004404Unknown or another client's id, or the file is missing
4290429The 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 -OJ https://barriers.dev.mtf.perpetuals.com:9800/v1/statements/12/pdf -H "X-Api-Key: bak_EXAMPLE"
GET/v1/statements/{id}/csvKey: READ

Download the CSV

Streams the CSV as an attachment. The COSTS kind offers no CSV and answers 404 with reason STATEMENT_FORMAT.

Path and query parameters
NameInTypeRequiredDescription
idpathnumberyesThe client's own statement id
Response example
text/csv attachment, Content-Disposition: attachment; filename="barriers-account-2026-07-01-2026-07-31-12.csv"
Errors
CodeHTTPWhen
4004404Unknown or another client's id, or the kind offers no CSV (STATEMENT_FORMAT: "This statement is not available in that format.")
4290429The 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 -OJ https://barriers.dev.mtf.perpetuals.com:9800/v1/statements/12/csv -H "X-Api-Key: bak_EXAMPLE"

Verification

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.

Response example
{
  "code": 0,
  "data": {
    "level": "MICA",
    "lifecycleState": "ACTIVE",
    "blocks": [
      { "code": "PERSONAL_INFO", "level": "MICA", "trigger": "ONBOARDING", "editable": true, "status": "APPROVED", "updatedAt": 1786800000000, "submittedAt": 1786700000000, "decidedAt": 1786800000000, "reason": null, "dataPresent": true, "version": 3, "data": { "firstName": "Alex", "lastName": "Trader" } }
    ],
    "identity": { "provider": "ONDATO", "providerRef": "8c9d0e1f-2a3b-4c5d-6e7f-8a9b0c1d2e3f", "sessionId": 7, "status": "VERIFIED", "sessionUrl": null, "expiresAt": 1786900000000, "completedAt": 1786820000000, "reason": null },
    "appropriateness": { "version": 2, "outcome": "PASS", "score": 9, "takenAt": 1786810000000, "acknowledgedAt": null, "retakeAt": 1786896400000, "requiresAcknowledgement": false, "attempt": 1 },
    "requirements": { "TRADE:PERP": ["APPROPRIATENESS"], "TRADE:SPOT": [], "TRADE:BARRIER": ["APPROPRIATENESS"], "TRADE:STAKING": ["APPROPRIATENESS"], "DEPOSIT:CRYPTO": [], "WITHDRAW:CRYPTO": [], "DEPOSIT:FIAT": ["BANK_ACCOUNT"], "WITHDRAW:FIAT": ["BANK_ACCOUNT"] },
    "edd": { "thresholdEur": "500000", "eurPerUsdc": "0.92", "depositedUsdc": "1500.000000", "depositedEur": "1380.00", "triggered": false, "required": false, "sourceOfWealth": "NOT_STARTED" },
    "mifidReview": "auto"
  }
}
Errors
CodeHTTPWhen
4010401No or unknown credential
4290429The 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 https://barriers.dev.mtf.perpetuals.com:9800/v1/verification -H "X-Api-Key: bak_EXAMPLE"
GET/v1/verification/requirementsKey: READ

What one action still requires

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.

Path and query parameters
NameInTypeRequiredDescription
actionquerystringyesTRADE:<family> (SPOT, STOCK, PERP, BARRIER, STAKING), DEPOSIT:CRYPTO, WITHDRAW:CRYPTO, DEPOSIT:FIAT or WITHDRAW:FIAT
Response example
{
  "code": 0,
  "data": {
    "ok": false,
    "action": "TRADE:PERP",
    "level": "MICA",
    "requiredLevel": "MICA_MIFID",
    "missingBlocks": ["ACCOUNT_PURPOSE", "EMPLOYMENT_FINANCIAL", "SOURCE_OF_WEALTH", "APPROPRIATENESS", "MIFID_DECLARATIONS"],
    "reason": "Complete your MiFID profile before trading perpetual contracts."
  }
}
Errors
CodeHTTPWhen
4000400An unknown action string
4290429The 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 "https://barriers.dev.mtf.perpetuals.com:9800/v1/verification/requirements?action=TRADE:PERP" -H "X-Api-Key: bak_EXAMPLE"
GET/v1/verification/blocks/{code}Key: READ

One verification block with its data

Path and query parameters
NameInTypeRequiredDescription
codepathstringyesThe block code (PERSONAL_INFO, IDENTITY_CHECK, TAX_RESIDENCY, BANK_ACCOUNT, SOURCE_OF_WEALTH, APPROPRIATENESS, ...)
Response example
{
  "code": 0,
  "data": {
    "code": "TAX_RESIDENCY",
    "level": "MICA",
    "trigger": "ONBOARDING",
    "editable": true,
    "status": "DRAFT",
    "updatedAt": 1787356800000,
    "submittedAt": null,
    "decidedAt": null,
    "reason": null,
    "dataPresent": true,
    "version": 2,
    "data": { "country": "DE", "tin": "12345678901" }
  }
}
Errors
CodeHTTPWhen
4004404Unknown verification section
4290429The 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 https://barriers.dev.mtf.perpetuals.com:9800/v1/verification/blocks/TAX_RESIDENCY -H "X-Api-Key: bak_EXAMPLE"
PUT/v1/verification/blocks/{code}Session only

Save a block as DRAFT

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
NameInTypeRequiredDescription
codepathstringyesA client-editable block code
Request body
NameTypeRequiredDescription
dataobjectyesThe block's fields (the object under data, or the body itself); validated per block, at most 64 KB
Response example
{
  "code": 0,
  "data": {
    "code": "TAX_RESIDENCY",
    "level": "MICA",
    "trigger": "ONBOARDING",
    "editable": true,
    "status": "DRAFT",
    "updatedAt": 1787356900000,
    "submittedAt": null,
    "decidedAt": null,
    "reason": null,
    "dataPresent": true,
    "version": 3,
    "data": { "country": "DE", "tin": "12345678901" }
  }
}
Errors
CodeHTTPWhen
4000400Field errors ("Please check the highlighted fields.", data.fields), a non-editable block, or a non-object body
4092409The block is SUBMITTED or APPROVED (locked)
4004404Unknown verification section
curl
curl -X PUT https://barriers.dev.mtf.perpetuals.com:9800/v1/verification/blocks/TAX_RESIDENCY -H "Authorization: Bearer <session-token>" -H "Content-Type: application/json" -d "{\"data\": {\"country\": \"DE\", \"tin\": \"12345678901\"}}"
POST/v1/verification/submitSession only

Submit a level for review

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.

Request body
NameTypeRequiredDescription
levelstringyesMICA or MIFID
Response example
{
  "code": 0,
  "data": {
    "level": "MICA",
    "lifecycleState": "KYC_PENDING",
    "status": "SUBMITTED",
    "submittedAt": 1787356800000
  }
}
Errors
CodeHTTPWhen
4000400The level word, or incomplete sections (data: {level, missingBlocks, fields, note, retakeAt})
4092409Already approved, or MIFID before MICA
curl
curl -X POST https://barriers.dev.mtf.perpetuals.com:9800/v1/verification/submit -H "Authorization: Bearer <session-token>" -H "Content-Type: application/json" -d "{\"level\": \"MICA\"}"
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.

Response example
{
  "code": 0,
  "data": {
    "code": "BANK_ACCOUNT",
    "level": "MICA",
    "trigger": "FIAT",
    "editable": true,
    "status": "SUBMITTED",
    "updatedAt": 1787356800000,
    "submittedAt": 1787356800000,
    "decidedAt": null,
    "reason": null,
    "dataPresent": true,
    "version": 2,
    "data": { "iban": "DE89370400440532013000", "accountHolder": "Alex Trader" }
  }
}
Errors
CodeHTTPWhen
4000400No 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.

Response example
{
  "code": 0,
  "data": {
    "code": "SOURCE_OF_WEALTH",
    "level": "MICA_MIFID",
    "trigger": "EDD",
    "editable": true,
    "status": "SUBMITTED",
    "updatedAt": 1787356800000,
    "submittedAt": 1787356800000,
    "decidedAt": null,
    "reason": null,
    "dataPresent": true,
    "version": 2,
    "data": { "sourceOfIncome": "EMPLOYMENT", "sourceOfWealth": "SAVINGS" }
  }
}
Errors
CodeHTTPWhen
4000400No 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).

Response example
{
  "code": 0,
  "data": [
    { "documentId": 5, "type": "passport", "side": "front", "filename": "passport.jpg", "mimeType": "image/jpeg", "sizeBytes": 482113, "uploadedAt": 1786700000000, "deletedAt": null, "source": "CLIENT" }
  ]
}
Errors
CodeHTTPWhen
4010401No or unknown credential
4290429The 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 https://barriers.dev.mtf.perpetuals.com:9800/v1/verification/documents -H "X-Api-Key: bak_EXAMPLE"
POST/v1/verification/documentsSession only

Upload a document (multipart)

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
NameTypeRequiredDescription
filefileyesmultipart/form-data; JPEG, PNG or PDF, at most 8 MB (the format is sniffed by magic bytes, not the extension)
typestringyespassport, national_id, drivers_license, residence_permit, utility_bill, bank_statement, w9_form, selfie, other, or a corporate document type
sidestringnofront or back, for two-sided identity documents
Response example
{
  "code": 0,
  "data": {
    "documentId": 6,
    "type": "utility_bill",
    "side": null,
    "filename": "bill.pdf",
    "mimeType": "application/pdf",
    "sizeBytes": 120400,
    "uploadedAt": 1787356800000,
    "source": "CLIENT"
  }
}
Errors
CodeHTTPWhen
4000400The document type, the file format, or an unreadable upload
4000413Above 8 MB ("The file is too large. The maximum size is 8 MB.")
4092409The identity lock, or a count cap (20 live, 3 per slot)
4290429More than 30 uploads in an hour
curl
curl -X POST https://barriers.dev.mtf.perpetuals.com:9800/v1/verification/documents -H "Authorization: Bearer <session-token>" -F "file=@passport.jpg" -F "type=passport" -F "side=front"
DELETE/v1/verification/documents/{id}Session only

Remove an uploaded document

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.

Path and query parameters
NameInTypeRequiredDescription
idpathnumberyesThe client's own document id
Response example
{
  "code": 0,
  "data": { "documentId": 6, "removed": true }
}
Errors
CodeHTTPWhen
4004404Unknown or another client's document
4092409The verification or identity lock, or an archived document
curl
curl -X DELETE https://barriers.dev.mtf.perpetuals.com:9800/v1/verification/documents/6 -H "Authorization: Bearer <session-token>"
POST/v1/verification/identity/sessionSession only

Start an identity session

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
NameTypeRequiredDescription
redirectUrlstringnoWhere the hosted flow returns the client afterwards
Response example
{
  "code": 0,
  "data": {
    "provider": "ONDATO",
    "providerRef": "8c9d0e1f-2a3b-4c5d-6e7f-8a9b0c1d2e3f",
    "sessionId": 7,
    "url": "https://sandbox-id.ondato.com/?id=8c9d0e1f-2a3b-4c5d-6e7f-8a9b0c1d2e3f",
    "status": "PENDING",
    "expiresAt": 1787443200000
  }
}
Errors
CodeHTTPWhen
4092409The identity check is already submitted or verified
5030503The provider is not configured or unreachable
curl
curl -X POST https://barriers.dev.mtf.perpetuals.com:9800/v1/verification/identity/session -H "Authorization: Bearer <session-token>" -H "Content-Type: application/json" -d "{}"
GET/v1/verification/identity/statusKey: READ

The identity session status

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.

Response example
{
  "code": 0,
  "data": {
    "provider": "ONDATO",
    "providerRef": "8c9d0e1f-2a3b-4c5d-6e7f-8a9b0c1d2e3f",
    "sessionId": 7,
    "status": "VERIFIED",
    "sessionUrl": null,
    "expiresAt": 1786900000000,
    "completedAt": 1786820000000,
    "reason": null
  }
}
Errors
CodeHTTPWhen
4010401No or unknown credential
4290429The 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 https://barriers.dev.mtf.perpetuals.com:9800/v1/verification/identity/status -H "X-Api-Key: bak_EXAMPLE"
POST/v1/verification/identity/webhookPublic

The identity provider's signed callback

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
CodeHTTPWhen
4000400A missing or wrong signature, or an unknown session
4000413Payload 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
CodeHTTPWhen
4010401No or unknown credential
4290429The 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 https://barriers.dev.mtf.perpetuals.com:9800/v1/verification/appropriateness/test -H "X-Api-Key: bak_EXAMPLE"
GET/v1/verification/textsKey: READ

The MiFID declaration sentences as served

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
CodeHTTPWhen
4000400family is absent or names no sentences family we serve (data.error TEXT_FAMILY_UNKNOWN)
4010401No or unknown credential
4290429The 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 "https://barriers.dev.mtf.perpetuals.com:9800/v1/verification/texts?family=DECLARATIONS_MIFID" -H "X-Api-Key: bak_EXAMPLE"
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
NameTypeRequiredDescription
versionnumbernoThe test version answered; a stale one is refused with 409 so the client re-reads the questions
answersobjectyesQuestion id to chosen option id
Response example
{
  "code": 0,
  "data": {
    "version": 2,
    "outcome": "WARN",
    "score": 5,
    "requiresAcknowledgement": true,
    "retakeAt": 1787443200000,
    "attempt": 1
  }
}
Errors
CodeHTTPWhen
4000400An incomplete or unknown answer set
4092409A stale test version, or the block is under review or approved
4290429A retake before the cooling period elapsed (data.retakeAt)
curl
curl -X POST https://barriers.dev.mtf.perpetuals.com:9800/v1/verification/appropriateness/answers -H "Authorization: Bearer <session-token>" -H "Content-Type: application/json" -d "{\"version\": 2, \"answers\": {\"q1\": \"c\"}}"
POST/v1/verification/appropriateness/acknowledgeSession only

Acknowledge the appropriateness warning

Acknowledges a WARN outcome, which completes the assessment for the MiFID submit.

Request body
NameTypeRequiredDescription
versionnumbernoThe test version of the WARN result
Response example
{
  "code": 0,
  "data": {
    "version": 2,
    "outcome": "WARN",
    "score": 5,
    "requiresAcknowledgement": false,
    "acknowledgedAt": 1787356800000,
    "attempt": 1
  }
}
Errors
CodeHTTPWhen
4000400There is no appropriateness warning to acknowledge
curl
curl -X POST https://barriers.dev.mtf.perpetuals.com:9800/v1/verification/appropriateness/acknowledge -H "Authorization: Bearer <session-token>" -H "Content-Type: application/json" -d "{\"version\": 2}"
GET/v1/kybKey: READ

The company verification file (LEGAL accounts)

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.

Response example
{
  "code": 0,
  "data": {
    "clientId": 12,
    "clientUid": "BARR-00000012",
    "entity": { "legalName": "Example Trading GmbH", "legalFormKind": "COMPANY", "registrationCountry": "DE", "lei": "529900T8BM49AURSDO55", "status": "DRAFT" },
    "status": "DRAFT",
    "persons": [
      { "personId": 1, "role": "UBO", "firstName": "Alex", "lastName": "Trader", "ownershipPct": "60", "active": true }
    ],
    "documents": [
      { "docId": 1, "kind": "certificate_of_incorporation", "title": "Certificate of incorporation", "refId": 6 }
    ],
    "blocks": [
      { "code": "KYB_ENTITY", "status": "DRAFT", "updatedAt": 1787356800000 }
    ],
    "complete": false,
    "missingBlocks": ["KYB_DOCUMENTS"],
    "problems": [],
    "leiGoodStanding": true,
    "uboThresholdPct": 25,
    "disclosureFloorPct": null,
    "higherRiskReasons": [],
    "uboCarveOut": null,
    "chart": { "nodes": [], "edges": [] },
    "documentKinds": ["certificate_of_incorporation", "register_extract"],
    "requiredDocumentKinds": ["certificate_of_incorporation", "register_extract", "articles_of_association", "ubo_register_extract"],
    "requiredDocumentGroups": [["certificate_of_incorporation"], ["register_extract"], ["articles_of_association"], ["ubo_register_extract", "shareholder_register"]],
    "legalFormKinds": ["COMPANY", "PARTNERSHIP", "TRUST", "FOUNDATION", "OTHER"],
    "roles": ["UBO", "DIRECTOR", "REPRESENTATIVE", "SHAREHOLDER_ENTITY"],
    "controlTypes": ["OWNERSHIP", "VOTING", "OTHER"],
    "powers": ["SOLE", "JOINT"],
    "powerSources": ["ARTICLES", "POA", "BOARD_RESOLUTION"],
    "events": null
  }
}
Errors
CodeHTTPWhen
4004404A personal account ("Your account is a personal account; there is no company file to complete.")
4290429The 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 https://barriers.dev.mtf.perpetuals.com:9800/v1/kyb -H "X-Api-Key: bak_EXAMPLE"
PUT/v1/kyb/entitySession only

Save the legal entity

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
NameTypeRequiredDescription
legalNamestringyesThe registered legal name
registrationNumberstringyesThe register number
registrationCountrystringyesISO 3166-1 alpha-2; screened like a residency
legalFormstringnoGmbH, Ltd, ... (a text naming a trust, foundation or partnership form is refused)
legalFormKindstringnoCOMPANY (the default), PARTNERSHIP, TRUST, FOUNDATION or OTHER; only a company can be onboarded at this time
leistringnoThe 20-character LEI
registeredAddressobjectnoThe registered address object
natureOfBusinessstringnoWhat the company does
taxIdstringnoThe tax identifier
Response example
{
  "code": 0,
  "data": {
    "entity": { "legalName": "Example Trading GmbH", "registrationNumber": "HRB 12345", "registrationCountry": "DE", "lei": "529900T8BM49AURSDO55", "status": "DRAFT" }
  }
}
Errors
CodeHTTPWhen
4000400Field errors, keyed by field in data
4092409The file is under review or approved
curl
curl -X PUT https://barriers.dev.mtf.perpetuals.com:9800/v1/kyb/entity -H "Authorization: Bearer <session-token>" -H "Content-Type: application/json" -d "{\"legalName\": \"Example Trading GmbH\", \"registrationNumber\": \"HRB 12345\", \"registrationCountry\": \"DE\"}"
POST/v1/kyb/personsSession only

Add an owner, director or representative

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
NameTypeRequiredDescription
rolestringyesUBO, DIRECTOR, REPRESENTATIVE or SHAREHOLDER_ENTITY
firstNamestringnoNatural person's first name (entity shareholders use entityName)
lastNamestringnoNatural person's last name
ownershipPctnumbernoThe ownership percentage of an owner
birthDatestringnoYYYY-MM-DD
nationalitystringnoISO country
isPepbooleannoPolitically exposed person flag
Response example
{
  "code": 0,
  "data": {
    "person": { "personId": 2, "role": "DIRECTOR", "firstName": "Dana", "lastName": "Director", "active": true }
  }
}
Errors
CodeHTTPWhen
4000400Field errors per role, keyed by field in data
4092409The file is under review or approved
curl
curl -X POST https://barriers.dev.mtf.perpetuals.com:9800/v1/kyb/persons -H "Authorization: Bearer <session-token>" -H "Content-Type: application/json" -d "{\"role\": \"DIRECTOR\", \"firstName\": \"Dana\", \"lastName\": \"Director\"}"
PUT/v1/kyb/persons/{personId}Session only

Edit a person of the company file

Edits an existing person with the same body as the create. A verification write, excluded for API keys.

Path and query parameters
NameInTypeRequiredDescription
personIdpathnumberyesThe person row to edit
Response example
{
  "code": 0,
  "data": {
    "person": { "personId": 2, "role": "DIRECTOR", "firstName": "Dana", "lastName": "Director", "active": true }
  }
}
Errors
CodeHTTPWhen
4004404Unknown person
4000400Field errors
4092409The file is under review or approved
curl
curl -X PUT https://barriers.dev.mtf.perpetuals.com:9800/v1/kyb/persons/2 -H "Authorization: Bearer <session-token>" -H "Content-Type: application/json" -d "{\"role\": \"DIRECTOR\", \"firstName\": \"Dana\", \"lastName\": \"Director\"}"
DELETE/v1/kyb/persons/{personId}Session only

Remove a person from the company file

Path and query parameters
NameInTypeRequiredDescription
personIdpathnumberyesThe person row to remove
Response example
{
  "code": 0,
  "data": { "personId": 2, "removed": true }
}
Errors
CodeHTTPWhen
4004404Unknown person
4092409The file is under review or approved
curl
curl -X DELETE https://barriers.dev.mtf.perpetuals.com:9800/v1/kyb/persons/2 -H "Authorization: Bearer <session-token>"
POST/v1/kyb/documentsSession only

Reference a corporate document

Attaches an uploaded corporate file to the company file under its document kind. A verification write, excluded for API keys.

Request body
NameTypeRequiredDescription
kindstringyescertificate_of_incorporation, register_extract, articles_of_association, shareholder_register, director_register, ubo_register_extract, board_resolution, power_of_attorney, financial_statements, lei_certificate or regulatory_licence
refIdnumberyesThe id of a file uploaded through POST /v1/verification/documents with the corporate type
titlestringnoA display title
issuedOnstringnoYYYY-MM-DD
expiresOnstringnoYYYY-MM-DD
notestringnoA short note
Response example
{
  "code": 0,
  "data": {
    "document": { "docId": 2, "kind": "register_extract", "title": "Register extract", "refId": 7 }
  }
}
Errors
CodeHTTPWhen
4000400An unknown kind or a refId that is not the client's live upload
4092409The file is under review or approved
curl
curl -X POST https://barriers.dev.mtf.perpetuals.com:9800/v1/kyb/documents -H "Authorization: Bearer <session-token>" -H "Content-Type: application/json" -d "{\"kind\": \"register_extract\", \"refId\": 7, \"title\": \"Register extract\"}"
DELETE/v1/kyb/documents/{docId}Session only

Remove a corporate document reference

Path and query parameters
NameInTypeRequiredDescription
docIdpathnumberyesThe document row of the company file
Response example
{
  "code": 0,
  "data": { "docId": 2, "removed": true }
}
Errors
CodeHTTPWhen
4004404Unknown document
4092409The file is under review or approved
curl
curl -X DELETE https://barriers.dev.mtf.perpetuals.com:9800/v1/kyb/documents/2 -H "Authorization: Bearer <session-token>"
POST/v1/kyb/submitSession only

Submit the company file for review

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.

Response example
{
  "code": 0,
  "data": { "status": "SUBMITTED", "submittedAt": 1787356800000 }
}
Errors
CodeHTTPWhen
4000400The file is incomplete (the answer names the missing blocks and problems)
4092409Already 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
NameInTypeRequiredDescription
localequerystringnoA 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 }
        ]
      }
    ]
  }
}
curl
curl "https://barriers.dev.mtf.perpetuals.com:9800/v1/support/faq?locale=en"
GET/v1/support/faq/suggestPublic

Ranked FAQ suggestions for a draft

Ranked published entries for the query, throttled per caller. The portal shows these before a request is submitted.

Path and query parameters
NameInTypeRequiredDescription
qquerystringyesThe draft text, at most 500 characters (empty answers no suggestions)
localequerystringnoDefault en
limitquerynumbernoAt 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
CodeHTTPWhen
4290429Too many searches ("Please wait a moment before searching the help centre again.")
curl
curl "https://barriers.dev.mtf.perpetuals.com:9800/v1/support/faq/suggest?q=withdrawal%20fee"
POST/v1/support/faq/feedbackPublic

Was this answer helpful

One row per click; the client id is recorded when a session rides along. Public like the FAQ page, throttled per caller.

Request body
NameTypeRequiredDescription
faqIdnumberyesA published entry
helpfulbooleanyesThe click
sourcestringnoFAQ or SUGGEST
querystringnoThe draft text that led to the suggestion, at most 500 characters
Response example
{
  "code": 0,
  "data": { "feedbackId": 91, "faqId": 7, "helpful": true, "source": "FAQ", "ts": 1787356800000 }
}
Errors
CodeHTTPWhen
4000400Missing faqId or helpful, or an unknown source
4004404Unknown or unpublished entry ("This FAQ entry was not found.")
4290429Too much feedback from one caller
curl
curl -X POST https://barriers.dev.mtf.perpetuals.com:9800/v1/support/faq/feedback -H "Content-Type: application/json" -d "{\"faqId\": 7, \"helpful\": true}"
GET/v1/support/categoriesPublic

The request categories

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.

Response example
{
  "code": 0,
  "data": {
    "categories": [
      { "code": "GETTING_STARTED", "name": "Getting started" },
      { "code": "VERIFICATION", "name": "Verification" },
      { "code": "DEPOSITS_WITHDRAWALS", "name": "Deposits and withdrawals" },
      { "code": "TRADING_PERPETUALS", "name": "Trading perpetuals" },
      { "code": "BARRIER_CONTRACTS", "name": "Barrier contracts" },
      { "code": "SPOT", "name": "Spot" },
      { "code": "STAKING", "name": "Staking" },
      { "code": "STATEMENTS_TAXES", "name": "Statements and taxes" },
      { "code": "SECURITY", "name": "Security" },
      { "code": "OTHER", "name": "Other" },
      { "code": "COMPLAINT", "name": "Complaint" }
    ],
    "relatedRefTypes": ["order", "trade", "swap", "statement", "barrier", "movement"]
  }
}
curl
curl https://barriers.dev.mtf.perpetuals.com:9800/v1/support/categories
POST/v1/support/requestsSession only

Send a support request

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
NameTypeRequiredDescription
categorystringyesA category code from GET /v1/support/categories (COMPLAINT opens a high-priority case and files the complaints register entry at the receipt)
subjectstringyesAt most 200 characters
messagestringyesAt most 4000 characters
relatedRefobjectno{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
suggestedFaqIdsnumber[]noThe FAQ suggestions the portal showed before the submit
Response example
{
  "code": 0,
  "data": {
    "requestId": 21,
    "caseId": 305,
    "ts": 1787356800000,
    "team": "SUPPORT",
    "category": "DEPOSITS_WITHDRAWALS",
    "subject": "Withdrawal still pending",
    "suggestedFaqIds": [7]
  }
}
Errors
CodeHTTPWhen
4000400The category, subject, message or related reference (the answer names the field)
4290429Too many requests in the last hour
5000500A 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.

Path and query parameters
NameInTypeRequiredDescription
limitquerynumbernoDefault 50
beforequerynumbernoKeyset cursor on the case id
Response example
{
  "code": 0,
  "data": {
    "requests": [
      {
        "id": 305,
        "caseId": 305,
        "requestId": 21,
        "title": "[Deposits and withdrawals] Withdrawal still pending",
        "status": "OPEN",
        "category": "DEPOSITS_WITHDRAWALS",
        "categoryName": "Deposits and withdrawals",
        "subject": "Withdrawal still pending",
        "relatedRef": { "type": "movement", "key": "41", "verified": true },
        "suggestedFaqIds": [7],
        "createdAt": 1787356800000,
        "updatedAt": 1787360000000,
        "closedAt": null,
        "messages": 2,
        "unread": 1,
        "lastMessage": { "ts": 1787360000000, "direction": "OUT", "preview": "Thanks, we are looking into it." },
        "firstResponse": { "state": "MET", "dueAt": 1787364000000, "at": 1787360000000 },
        "attachments": 0,
        "fromClient": true
      }
    ],
    "nextBefore": 305,
    "hasMore": false
  }
}
Errors
CodeHTTPWhen
4000400The page size or the cursor
4290429The 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 "https://barriers.dev.mtf.perpetuals.com:9800/v1/support/requests?limit=50" -H "X-Api-Key: bak_EXAMPLE"
GET/v1/support/requests/{caseId}Key: READ

One request's thread

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
NameInTypeRequiredDescription
caseIdpathnumberyesA 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
CodeHTTPWhen
4004404A case not sent to this client ("This request was not found.")
4290429The 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 https://barriers.dev.mtf.perpetuals.com:9800/v1/support/requests/305 -H "X-Api-Key: bak_EXAMPLE"
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
NameInTypeRequiredDescription
caseIdpathnumberyesThe client's own request, OPEN or closed within the last 30 days
Request body
NameTypeRequiredDescription
filefileyesmultipart/form-data, the case-file formats and size caps
Response example
{
  "code": 0,
  "data": { "caseId": 305, "fileId": 12, "ts": 1787356800000 }
}
Errors
CodeHTTPWhen
4004404Not the client's request
4092409The case was closed more than 30 days ago ("This case was closed more than 30 days ago. Please send a new request.")
4290429The 5-file cap per request or the hourly upload throttle
4000400The file format or size
curl
curl -X POST https://barriers.dev.mtf.perpetuals.com:9800/v1/support/requests/305/files -H "Authorization: Bearer <session-token>" -F "file=@screenshot.png"
GET/v1/support/requests/{caseId}/files/{fileId}Key: READ

Download an own attachment

Streams the client's OWN attachment (never an employee's file) as an attachment with nosniff.

Path and query parameters
NameInTypeRequiredDescription
caseIdpathnumberyesThe client's own request
fileIdpathnumberyesA file the client uploaded on it
Response example
Binary attachment with the stored content type, Content-Disposition: attachment; filename="screenshot.png"
Errors
CodeHTTPWhen
4004404Not the client's request or not its own file
4290429The 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 -OJ https://barriers.dev.mtf.perpetuals.com:9800/v1/support/requests/305/files/12 -H "X-Api-Key: bak_EXAMPLE"
GET/v1/casesKey: READ

The client's case list

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.

Path and query parameters
NameInTypeRequiredDescription
limitquerynumbernoDefault 50
beforequerynumbernoKeyset cursor on the case id
Response example
{
  "code": 0,
  "data": {
    "cases": [
      { "id": 305, "caseId": 305, "title": "[Deposits and withdrawals] Withdrawal still pending", "status": "OPEN", "updatedAt": 1787360000000, "closedAt": null, "messages": 2, "lastMessageAt": 1787360000000, "unread": 1 }
    ],
    "nextBefore": 305,
    "hasMore": false
  }
}
Errors
CodeHTTPWhen
4000400The page size or the cursor
4290429The 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 "https://barriers.dev.mtf.perpetuals.com:9800/v1/cases?limit=50" -H "X-Api-Key: bak_EXAMPLE"
GET/v1/cases/{caseId}Key: READ

One case thread

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
NameInTypeRequiredDescription
caseIdpathnumberyesA 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
CodeHTTPWhen
4004404A case not sent to this client
4290429The 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 https://barriers.dev.mtf.perpetuals.com:9800/v1/cases/305 -H "X-Api-Key: bak_EXAMPLE"
POST/v1/cases/{caseId}/replySession only

Reply on a case

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.

Path and query parameters
NameInTypeRequiredDescription
caseIdpathnumberyesA case sent to this client
Request body
NameTypeRequiredDescription
textstringyesAt most 4000 characters
Response example
{
  "code": 0,
  "data": { "caseId": 305, "ts": 1787360500000 }
}
Errors
CodeHTTPWhen
4000400Missing or oversized text ("Please write a message (at most 4000 characters).")
4004404A case not sent to this client
4092409The case was closed more than 30 days ago ("This case was closed more than 30 days ago. Please send a new request.")
4290429The per-case reply cap
curl
curl -X POST https://barriers.dev.mtf.perpetuals.com:9800/v1/cases/305/reply -H "Authorization: Bearer <session-token>" -H "Content-Type: application/json" -d "{\"text\": \"Thank you, resolved.\"}"

Notifications and devices

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".

Path and query parameters
NameInTypeRequiredDescription
limitquerynumberno1 to 100, default 20
Response example
{
  "code": 0,
  "data": {
    "unread": 2,
    "items": [
      {
        "id": "MONEY_MOVEMENT:40",
        "kind": "MONEY_MOVEMENT",
        "ref": "40",
        "ts": 1787300000000,
        "title": "Deposit of 500.000000 USDC credited",
        "link": "/money",
        "read": false,
        "data": { "movementId": 40, "direction": "IN", "asset": "USDC", "amount": "500.000000", "state": "CONFIRMED" }
      }
    ],
    "lookbackDays": 90,
    "asOf": 1787356800000
  }
}
Errors
CodeHTTPWhen
4010401No or unknown credential
4290429The 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 "https://barriers.dev.mtf.perpetuals.com:9800/v1/notifications?limit=20" -H "X-Api-Key: bak_EXAMPLE"
POST/v1/notifications/readSession only

Mark notifications read

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
NameTypeRequiredDescription
idsstring[]noItem ids ("KIND:ref"), at most 200 per call; markers are only written for items of the client's own feed
allbooleannoMark everything through now read (the watermark; a later item is unread again)
Response example
{
  "code": 0,
  "data": { "marked": 1, "all": false, "unread": 1, "ts": 1787356800000 }
}
Errors
CodeHTTPWhen
4000400Neither ids nor all, an unknown kind or a non-numeric ref ("Please name the notifications to mark read, or mark all.")
4290429More than 60 read posts per minute
curl
curl -X POST https://barriers.dev.mtf.perpetuals.com:9800/v1/notifications/read -H "Authorization: Bearer <session-token>" -H "Content-Type: application/json" -d "{\"ids\": [\"MONEY_MOVEMENT:40\"]}"
GET/v1/notifications/prefsKey: READ

The push preferences per kind

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.

Response example
{
  "code": 0,
  "data": {
    "kinds": [
      { "kind": "CASE_REPLY", "enabled": true },
      { "kind": "STATEMENT_READY", "enabled": true },
      { "kind": "VERIFICATION_DECISION", "enabled": true },
      { "kind": "MONEY_MOVEMENT", "enabled": true },
      { "kind": "SWAP_SETTLED", "enabled": true },
      { "kind": "SWAP_DEFAULTED", "enabled": true },
      { "kind": "RESTRICTION_SET", "enabled": true },
      { "kind": "RESTRICTION_LIFTED", "enabled": false },
      { "kind": "SWAP_WRITTEN_OFF", "enabled": true },
      { "kind": "SWAP_RECOVERED", "enabled": true }
    ]
  }
}
Errors
CodeHTTPWhen
4010401No or unknown credential
4290429The 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 https://barriers.dev.mtf.perpetuals.com:9800/v1/notifications/prefs -H "X-Api-Key: bak_EXAMPLE"
PUT/v1/notifications/prefsSession only

Set one push preference

Upserts the row. A notification write, excluded for API keys.

Request body
NameTypeRequiredDescription
kindstringyesOne of the bell's notification kinds
enabledbooleanyesWhether push delivery of the kind is on
Response example
{
  "code": 0,
  "data": {}
}
Errors
CodeHTTPWhen
4000400An unknown kind ("This notification kind is not known.") or a missing enabled
curl
curl -X PUT https://barriers.dev.mtf.perpetuals.com:9800/v1/notifications/prefs -H "Authorization: Bearer <session-token>" -H "Content-Type: application/json" -d "{\"kind\": \"RESTRICTION_LIFTED\", \"enabled\": false}"
POST/v1/devicesSession only

Register a mobile device for push

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
NameTypeRequiredDescription
platformstringyesStrictly ANDROID or IOS
tokenstringyesThe FCM or APNs device token, 1 to 4096 characters
appBuildnumbernoThe app's build number
Response example
{
  "code": 0,
  "data": { "deviceId": 3 }
}
Errors
CodeHTTPWhen
4000400The platform ("The platform must be ANDROID or IOS."), the token, or the 20-device cap
4290429More than 30 registrations per minute
4034403Called with an API key (excluded namespace)
curl
curl -X POST https://barriers.dev.mtf.perpetuals.com:9800/v1/devices -H "Authorization: Bearer <session-token>" -H "Content-Type: application/json" -d "{\"platform\": \"ANDROID\", \"token\": \"fcm-token\", \"appBuild\": 12}"
DELETE/v1/devicesSession only

Unregister a device (sign-out path)

Removes the calling client's row for the token and its unsent push rows. For the mobile apps; excluded for API keys.

Request body
NameTypeRequiredDescription
tokenstringnoThe device token to remove; an absent or unknown token deletes nothing (idempotent)
Response example
{
  "code": 0,
  "data": {}
}
Errors
CodeHTTPWhen
4010401No session
curl
curl -X DELETE https://barriers.dev.mtf.perpetuals.com:9800/v1/devices -H "Authorization: Bearer <session-token>" -H "Content-Type: application/json" -d "{\"token\": \"fcm-token\"}"