Beyin Finance Developer API Reference
Base URL: https://api.beyinfinance.com
All endpoints in this reference are relative to this base URL (for example,
POST https://api.beyinfinance.com/user?request_type=account_info).
Authentication
Use developer API credentials for integrations. Official app/web clients may use the authenticated session token issued at login.
Client |
Authentication |
|---|---|
Developer/integration client |
|
Trading Data routes |
|
Developer API-key example:
X-API-Key: bf_key_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
X-API-Secret: bf_sec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
Generate API keys from the Telegram bot, web dashboard, or mobile app.
A missing or invalid credential returns HTTP 401. Every /tradingdata, /user,
and /backtest operation in this reference requires a tracked caller identity
unless the endpoint is explicitly part of login or registration.
Note: Some Trading Data endpoints allow unauthenticated (guest) access with stricter IP-based rate limits. Guest-accessible endpoints include:
trend_signals,trend_signal_detail,trend_indicator,trend_indicator_history,market_ticker,get_klines,market_quote,market_symbol_info,platform_notifications,community_posts,community_chat,community_leaders,marketplace_browse,marketplace_listing, andmarketplace_reviews.
A 401 caused by the session token itself carries "code": "session_expired" in
the error body; a 401 caused by a rejected or revoked Developer API key carries
"code": "invalid_credentials". Not every 401 is a session problem — see the
Errors section for the marker table and the rule for when a client should
re-authenticate.
If the Developer API key store cannot be reached, the request never reaches a
credential decision: it returns HTTP 503 with
"code": "api_key_store_unavailable". That is an outage to retry, not a
credential to replace.
Telegram Bot Scope
The Telegram bot is an account handoff and notification channel. It can create or link a Beyin Finance account, show account and plan information, deliver signals, manage signal automation preferences, and guide manually confirmed Binance order actions. It is not a separate market-data source, and clients must still use the Beyin Finance API endpoints documented here.
Request correlation
A correlation ID is a short identifier you attach to a request so you can trace it end-to-end — in your own logs and when reporting an issue to support. Send one logical ID per operation and reuse the same value across automatic retries of that operation, so a retried request is recognisable as the same logical call rather than a new one.
Send it in the X-Correlation-ID request header. The value must be 8–128
characters and may contain only letters, digits, and the characters
. _ : -. If you send an invalid or empty value, the API ignores it and
generates its own ID (prefixed bf-) so a correlation ID is always present.
The accepted (or generated) value is echoed back in the X-Correlation-ID
response header, and it is listed in Access-Control-Expose-Headers so
browser-based clients can read it.
Request:
POST /user?request_type=account_info
X-Correlation-ID: acct-load-2f9a1c7e
Response headers:
X-Correlation-ID: acct-load-2f9a1c7e
Access-Control-Expose-Headers: X-Correlation-ID,X-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-Reset
If you had sent X-Correlation-ID: ab (too short) or omitted the header, the
response would instead carry a server-generated value such as
X-Correlation-ID: bf-17c3f9a1b2d4e5f6.
Rate Limits
Rate limits can vary by account and endpoint. Use the response headers as the authoritative limit for the authenticated client.
Rate Limit Headers
Every response includes rate limit information:
X-RateLimit-Limit: 120 # Your per-minute limit
X-RateLimit-Remaining: 87 # Requests remaining in current window
X-RateLimit-Reset: 1784990340 # Unix timestamp when the window resets
When rate limited (HTTP 429):
Retry-After: 15 # Seconds until reset
Plan Limits
Plan |
Monthly |
Annual (-50%) |
Rate Limit (req/min) |
Max Active Strategies |
Max Coins/Strategy |
|---|---|---|---|---|---|
Free |
– |
– |
30 |
0 |
0 |
Starter |
$10 |
$60 |
30 |
1 |
5 |
Plus |
$20 |
$120 |
60 |
2 |
10 |
Pro |
$50 |
$300 |
120 |
5 |
25 |
Investor |
$100 |
$600 |
240 |
10 |
50 |
First-party (app/web) requests receive 2× the listed rate limit.
Payment
Plans are sold as one-time purchases that extend your subscription — there is no automatic recurring billing on the manual channels. Each successful payment adds a fixed period to your plan: a monthly (1 month / “1 Ay”) purchase adds 30 days and an annual (1 year / “1 Yıl”) purchase adds 365 days, stacked on top of any time you already have. To keep a plan active you make another purchase before it expires.
Every purchase must be tied to your account by your 6-character Beyin ID
(for example MTHG7A), which you can find in your profile / account info.
Card payment — Shopier
Pay by card through the Beyin Finance Shopier store:
Open the store, pick the listing for the plan and period you want (each plan has a separate monthly / annual listing), and complete checkout.
Important: during checkout, type your 6-character Beyin ID into the order note field. This is how the payment is matched to your account. If you are unsure the note was saved, keep your order email so support can match it by e-mail as a fallback.
Crypto — Binance Pay (manual)
Send USDT to the Beyin Finance Binance Pay account:
Binance Pay ID:
863 826 81Important: paste only your 6-character Beyin ID into the transfer Note / Remark field so the payment is matched to your account. If your Binance account is already linked to Beyin Finance, matching happens automatically from the payer identity.
Mobile — in-app purchase
In the iOS and Android apps, plans are sold as store-managed subscriptions:
Android: Google Play Billing. iOS: Apple In-App Purchase (StoreKit).
Products are the plan × period SKUs (
beyin_<plan>_monthly/beyin_<plan>_annual); a single education product is sold as a one-time purchase.Your Beyin ID is bound to the purchase automatically by the app, so there is no note to fill in. Every purchase is verified server-side with the store before your plan is granted — the app does not unlock a plan on the device alone.
Unlike the manual channels above, store subscriptions auto-renew through the App Store / Google Play until you cancel them in the store. Renewals and refunds are applied to your Beyin Finance plan automatically.
Client Best Practices
Read
X-RateLimit-Remainingfrom every responseIf
Remaining< 5, slow down or delay requestsOn 429, wait
Retry-Afterseconds before retryingCache responses when possible (e.g.
account_info,available_coins)
Backtest Job Status Response Schema
POST /backtest?action=status
{
"action": "status",
"job_id": "job_1784990340",
"status": "running",
"progress_pct": 45,
"coin_progress": {
"BTC": {"status": "completed", "progress_pct": 100},
"ETH": {"status": "running", "progress_pct": 45}
}
}
When a request exceeds the current limit, the API returns HTTP 429 with a
Retry-After header:
Rate Limit Response (HTTP 429):
{
"error": "Too many requests. Please wait before trying again.",
"reason": "rate_limit_exceeded",
"retry_after": 15
}
Service Temporarily Unavailable (HTTP 503):
When the rate-limit backend is momentarily unreachable, credit-consuming operations are rejected with a short cool-off rather than being processed without accounting:
{
"error": "Rate limit store is unavailable; please retry shortly",
"reason": "temporarily_unavailable",
"retry_after": 5
}
Common reason codes:
rate_limit_exceeded(HTTP 429): Your per-minute request limit was exceeded.temporarily_unavailable(HTTP 503): The request cannot be processed right now (e.g. the rate-limit backend is briefly unavailable); retry after the indicated delay.backtest_concurrency_limit(HTTP 429): You already have the maximum number of backtest jobs running at once. The cap adapts to current platform load, so it can be lower when the system is busy. Wait for a running job to finish (pollaction=status) before launching another. Only backtest launches (action=run/full_range/portfolio) can return this — status polling, results, and every other action are never limited by it. The body also includescurrent_runningandmax_concurrent.strategy_generation_in_progress(HTTP 429): You already have the maximum number of AI strategy generations running (/user?action=strategy_generate). Wait for one to finish before starting another. The body also includesin_progressandmax_concurrent.backtest_capacity_exceeded(HTTP 429): The backtest engine hit a transient dispatch capacity limit. This is rare and short-lived; retry after the indicated delay.
Recommended Handling:
Inspect the Retry-After header (or the retry_after JSON field, in seconds)
and apply exponential backoff with random jitter before retrying.
These
reasoncodes and theRetry-After/retry_aftercontract apply to the request-facing REST endpoints (the User API and backtest API). Real-time WebSocket channels signal overload with a plain429close/response without aRetry-Aftervalue — back off client-side when you see one.
Account
Get Account Info
POST /user?request_type=account_info
No body required.
Response:
{
"ok": true,
"data": {
"beyin_id": "MTHG7A",
"plan": "pro",
"beyin_credits": 7.35,
"license_expires_at": 1790000000,
"demo_expires_at": 0,
"active_own_strategies": 3,
"community_role": "leader",
"connections": {
"binance": true,
"telegram": false,
"google": true
},
"notification_settings": {
"app": true,
"announcement": true,
"account": true,
"bot": true,
"telegram": false,
"mail": false
},
"referral": {
"code": "BF-REF-123",
"commissions": [
{
"commission_id": "payment-1",
"amount_usdt": 1.25,
"status": "recorded"
}
],
"total_commission_usdt": 1.25
},
"strategies": {
"emacross": {
"name": "emacross",
"status": "active",
"candle_count": 32,
"market_type": "spot",
"leverage": 1
}
}
}
}
Connection values are booleans only. Referral entries use the documented identifier, amount and status fields.
Get Login History
POST /user?request_type=login_history
Returns the most recent known login for each retained IP origin, newest first. The authentication record keeps a bounded set of origins; repeated logins from the same IP update that origin rather than creating duplicate events.
Field |
Type |
Required |
Description |
|---|---|---|---|
|
integer |
No |
Default 25, min 1, max 100 |
|
string |
No |
Opaque cursor returned by the previous page |
{
"ok": true,
"data": {
"logins": [
{
"login_id": "login#abc...",
"ip_address": "192.0.2.10",
"platform": "android",
"login_at": 1784936800
}
],
"count": 1,
"last_evaluated_key": "base64...",
"has_more": true
}
}
login_id is the stable duplicate-removal key. Return the opaque cursor
unchanged; malformed cursors and non-integer page sizes return HTTP 400.
Get Credits History
POST /user?request_type=credits_history
Returns signed credit movements from all available monthly logs and deposits,
newest first. Positive amount values add credits; negative values consume
credits.
Field |
Type |
Required |
Description |
|---|---|---|---|
|
integer |
No |
Default 50, min 1, max 100 |
|
string |
No |
Opaque cursor returned by the previous page |
Response:
{
"ok": true,
"data": {
"transactions": [
{"transaction_id": "credit#abc...", "type": "adjustment", "amount": 0.05, "action": "credit_adjustment", "description": "Credit adjustment", "timestamp": 1784936800},
{"transaction_id": "credit#def...", "type": "spend", "amount": -0.01, "action": "economic_news", "description": "Economic news fetch", "timestamp": 1784936700},
{"transaction_id": "deposit#payment-1", "type": "deposit", "amount": 5.0, "action": "deposit", "description": "Payment abc12345", "timestamp": 1784900000}
],
"count": 3,
"last_evaluated_key": "base64...",
"has_more": true
}
}
Transaction actions: deposit, backtest, credit_adjustment, strategy_generate, strategy_refund, marketplace_signal
Automatic recovery details and adjustment reasons are not exposed through the
customer API. Clients should display credit_adjustment as a generic balance
correction and rely on the signed amount.
The deterministic transaction_id is the duplicate-removal key. Return the
opaque cursor unchanged; malformed cursors and non-integer page sizes return
HTTP 400.
Get Binance Balance
POST /user?request_type=binance_balance
Requires Binance API keys linked. No body params.
Response:
{
"ok": true,
"data": {
"spot": {"BTC": 0.001, "USDT": 250.0},
"futures": {"USDT": 100.0},
"funding": {"USDT": 50.0}
}
}
Get Order History
POST /user?request_type=order_history
Field |
Type |
Required |
Description |
|---|---|---|---|
|
string |
No |
|
|
integer |
No |
Default 25, min 1, max 100 |
|
string |
No |
Opaque cursor returned by the previous page |
Records are ordered newest first. order_history_id is the stable
duplicate-removal key. Return the opaque cursor unchanged; malformed cursors,
invalid statuses and non-integer page sizes return HTTP 400. Undocumented fields and
stored exchange/credential payloads are removed from the response.
Response:
{
"ok": true,
"data": {
"orders": [
{
"order_id": "ord_1784936800",
"timestamp": 1784936800,
"strategy_name": "emacross",
"coin": "BTC",
"exchange_order_status": "NEW",
"execution_mode": "real_trade",
"order_source": "signal",
"signal_position_key": "pos_1784850000"
}
],
"count": 1,
"status": "active",
"last_evaluated_key": "base64...",
"has_more": true
}
}
Preview Binance OCO Order
POST /user?request_type=binance_order_preview
Uses the same request fields as binance_order, but does not place an
exchange order. The API applies exchange precision rules, validates TP/SL
direction, checks minimum notional requirements and verifies the authenticated
account’s available balance before returning the irreversible-operation
summary. Order placement is supported for Spot only — see the prohibition
note on binance_order below. A successful preview is not a balance
reservation; execution repeats validation because market/account state can
change.
Response:
{
"ok": true,
"data": {
"symbol": "BTCUSDT",
"position_side": "BUY",
"order_side": "SELL",
"quantity": "0.001",
"take_profit_price": "67000.00",
"stop_loss_price": "63000.00",
"price_precision": 2,
"quantity_precision": 3,
"minimum_notional": 10,
"balance_asset": "BTC",
"available_balance": "0.010",
"required_balance": "0.001",
"balance_verified": true,
"market_type": "spot",
"leverage": 1,
"order_source": "signal",
"signal_position_key": "pos_1784850000",
"irreversible": true
}
}
market_type is always "spot" and leverage is always 1; futures order
placement is not permitted (see below).
Use price_precision and quantity_precision from this response to format all price
and quantity strings in the subsequent binance_order request.
The client must obtain a successful preview and require explicit user
confirmation before calling binance_order.
Place Binance OCO Order
POST /user?request_type=binance_order
Places an OCO (One-Cancels-Other) order on Binance. Combines a Take-Profit limit order and a Stop-Loss stop-limit order. When one triggers, the other is automatically cancelled.
Requires Binance API keys linked.
Important
Before using this endpoint, the user’s Binance API key must have the Enable Spot & Margin Trading permission enabled in Binance. The key must also restrict access to trusted IPs and include the Beyin Finance Binance proxy static IP in the Binance API whitelist:
3.120.214.198
Orders sent through Beyin Finance are routed to Binance from this static IP, so Binance may reject order placement if the IP is not whitelisted.
Field |
Type |
Required |
Description |
|---|---|---|---|
|
string |
Yes |
e.g. |
|
string |
Yes |
|
|
string |
Yes |
Amount to sell/buy when TP or SL triggers (formatted to |
|
string |
No |
Optional reference entry price (formatted to |
|
string |
Yes |
Take-profit limit price (formatted to |
|
string |
Yes |
Stop-loss trigger price (formatted to |
|
string |
No |
Must be |
|
string |
Yes |
|
|
string |
Conditional |
Opaque signal position key required for |
|
string |
Yes |
16-128 letters, numbers, |
Warning
All price and quantity values must be formatted as strings using the exchange precision
returned by binance_order_preview. Use price_precision for all price fields and
quantity_precision for quantity. Binance rejects orders with incorrect decimal precision.
Example: If price_precision: 2 → send "67000.00" not 67000 or "67000.001".
If quantity_precision: 3 → send "0.001" not 0.001 or "0.0010".
Note
Prices and quantities must be sent as strings formatted to the exchange precision returned by preview. The API validates precision and rejects misformatted values.
During preview and immediately before a real submission, the API validates account balance and order constraints again.
The same idempotency key must be reused for retries of the same logical order.
For
order_source=signal, provide the opaquesignal_position_keyreturned by the signal endpoint.
Warning
Futures order placement is prohibited. For legal and regulatory
compliance, Beyin Finance does not send futures orders to the exchange. Only
spot orders can be placed through binance_order / binance_order_preview; a
request with market_type="futures" is rejected with HTTP 403. Futures
market data (reading prices, klines, symbol info) remains available, and
futures may still be used in backtesting — only live futures order
submission is blocked.
Example body:
{
"symbol": "BTCUSDT",
"side": "BUY",
"quantity": "0.001",
"entry_price": "65000.00",
"take_profit_price": "67000.00",
"stop_loss_price": "63000.00",
"market_type": "spot",
"order_source": "signal",
"signal_position_key": "pos_1784850000",
"idempotency_key": "idem_1784990000"
}
Response:
{
"ok": true,
"data": {
"order_list_id": "12345678",
"client_order_id": "exchange_client_order_id",
"symbol": "BTCUSDT",
"side": "SELL",
"quantity": "0.00100",
"take_profit_price": "67000.00",
"stop_loss_price": "63000.00",
"orders": [
{"symbol": "BTCUSDT", "orderId": 111, "type": "LIMIT_MAKER"},
{"symbol": "BTCUSDT", "orderId": 222, "type": "STOP_LOSS_LIMIT"}
],
"signal_position_key": "pos_1784850000",
"order_source": "signal",
"idempotency_key": "idem_1784990000",
"status": "submitted"
}
}
Repeating the same idempotency key for the same logical order returns the
stored response with idempotent_replay=true.
{"ok": true, "data": {"status": "processing", "idempotency_key": "idem_1784990000", "message": "Order submission is being verified."}}
The client must not create a new key or blindly resubmit while an order is in a processing state. Show the order status to the user and poll order history.
Errors:
400: Missing/non-finite/non-numeric fields, invalid source/link combination, Futures market, precision/minimum-notional failure, insufficient available balance, or invalid idempotency key
404: A linked active signal does not exist for the authenticated user
409: The key was reused with a different order, or the linked signal does not match the symbol
503: Balance or order submission could not be verified; the order was not submitted
Get Notifications
POST /user?request_type=notifications_list
Returns personal notifications newest first.
Field |
Type |
Required |
Description |
|---|---|---|---|
|
integer |
No |
Default 50, min 1, max 100 |
|
string |
No |
Opaque cursor returned by the previous page |
Response:
{
"ok": true,
"data": {
"notifications": [
{"id": "notification_1784900000", "title": "Backtest Complete", "body": "Your BTC 4h backtest finished", "event_type": "backtest_complete", "timestamp": 1784900000}
],
"count": 1,
"last_evaluated_key": "base64...",
"has_more": true
}
}
The cursor encodes the last (timestamp, id) ordering key and must be returned
unchanged. has_more=false with a null cursor is the final page. A malformed
cursor or non-integer page size returns HTTP 400.
Mark Notifications Read
POST /user?request_type=notifications_mark_read
Mark one notification:
{"notification_id": "notification_1784900000"}
Response:
{"ok": true, "data": {"notification_id": "notification_1784900000", "read": true}}
Mark every unread notification:
{"all": true}
Response:
{"ok": true, "data": {"marked_read": 4}}
Exactly one of notification_id or all=true is required. Unknown notification
IDs return 404.
List Referred Users
POST /user?request_type=referral_list
No body required. Returns the users who registered using the caller’s Beyin ID as their reference code, newest first. Referred users’ IDs are masked (first two and last two characters visible) — full IDs are never exposed.
Response:
{
"ok": true,
"data": {
"referrals": [
{
"beyin_id_masked": "AB**23",
"joined_at": 1784900000,
"plan": "starter"
}
],
"count": 1
}
}
joined_at is a Unix timestamp in seconds. plan is the referred user’s
current plan identifier (free when no paid plan is active).
Redeem Referral Commission
POST /user?request_type=referral_redeem
Converts available (unredeemed) referral commission into Beyin Credits, instantly. You earn a commission on referred users’ payments — the default rate is 20% and may be adjusted per account — and the credited balance is spendable inside Beyin Finance the same as any other credit.
Commission is redeemed as credits only — there is no cash/USDT withdrawal and therefore no payout or KYC step for referral earnings. (Instructor course earnings are a separate system with its own payout flow; see the Education API reference.)
Field |
Type |
Required |
Description |
|---|---|---|---|
|
string |
Yes |
Must be |
|
number |
Yes |
USDT-denominated amount, minimum 1, at most the available balance |
Response:
{"ok": true, "data": {"redeem_type": "credits", "amount": 5.0, "status": "completed"}}
Sending any redeem_type other than credits, or an amount below 1, returns
HTTP 400.
Set Inviter / Referral ID
POST /user?request_type=set_inviter
Sets the 6-character Beyin ID of the user who invited the caller. This endpoint is only available if no referral ID has been set previously and the user registered within the last 30 days.
Field |
Type |
Required |
Description |
|---|---|---|---|
|
string |
Yes |
6-character uppercase alphanumeric Beyin ID of the inviter |
Validation Rules:
Must be a valid 6-character alphanumeric code (
^[A-Z0-9]{6}$).Cannot be the caller’s own Beyin ID.
Cannot be changed once set.
Must be submitted within 30 days of the user’s registration timestamp (
registered_at).The inviter Beyin ID must exist in the system.
Response:
{
"ok": true,
"data": {
"inviter_id": "ABC123",
"message": "Inviter ID set successfully"
},
"timestamp": 1787098000
}
User Preferences & Favorites
Update Favorite Coins
POST /user?request_type=favorite_coin
Adds or removes a cryptocurrency trading pair from the user’s synced favorites list stored in their profile.
Field |
Type |
Required |
Description |
|---|---|---|---|
|
string |
Yes |
Asset pair symbol, e.g. |
|
string |
No |
|
Validation Rules:
symbolis required and automatically converted to uppercase.Maximum 100 favorite coins allowed per user profile.
Response:
{
"ok": true,
"data": {
"symbol": "BTCUSDT",
"action": "add",
"favorites": ["BTCUSDT", "ETHUSDT", "SOLUSDT"]
},
"timestamp": 1787098000
}
Developer API Key Management
Manage your Developer API key. Each user may have 1 active Developer API key at a time — to replace it, revoke the existing key and generate a new one. These endpoints require JWT authentication (API key auth is not allowed for key management operations).
Generate API Key
POST /user?request_type=api_key_generate
Creates a new Developer API key. The generated secret is returned once and cannot be retrieved afterward.
Field |
Type |
Required |
Description |
|---|---|---|---|
|
string |
Yes |
Human-readable label for the key (1-64 characters) |
|
string[] |
No |
Permission scopes. Default: |
Response:
{
"ok": true,
"data": {
"api_key": "bf_key_a1b2c3d4e5f6a1b2c3d4e5f6",
"api_secret": "bf_sec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
}
Warning
The api_secret is shown once only — store it securely. It is not retrievable
afterward.
Errors:
400: “Label must be 1-64 characters”
400: “Maximum 1 API key allowed. Delete your existing key first.”
403: “api_key_generate requires JWT authentication”
503: API key store unavailable (
"code": "api_key_store_unavailable"). No key was created and no secret was issued; retry the request.
Get API Key Status
POST /user?request_type=api_key_status
Returns the status of your single active Developer API key. Because each user may hold only one key, this reports whether a key exists and its metadata rather than a list. The secret is never returned by this operation.
Response:
{
"ok": true,
"data": {
"has_key": true,
"key": {
"api_key": "bf_key_a1b2c3d4e5f6a1b2c3d4e5f6",
"label": "Trading dashboard",
"permissions": ["read", "trade"],
"created_at": 1787098000
},
"max_keys": 1
}
}
When no key exists, has_key is false and key is null.
Errors:
503: API key store unavailable (
"code": "api_key_store_unavailable"); retry with backoff.
Revoke API Key
POST /user?request_type=api_key_revoke
Revokes your Developer API key. The key record is deleted permanently and
immediately frees your key slot so you can generate a replacement.
Revocation cannot be undone — issue a new key with api_key_generate.
Field |
Type |
Required |
Description |
|---|---|---|---|
|
string |
Yes |
The API key ID to revoke |
Response:
{"ok": true, "data": {"deleted": true}}
Errors:
400: “api_key_id required”
403: “api_key_revoke requires JWT authentication”
403: “Key not found or not owned by user”
503: API key store unavailable (
"code": "api_key_store_unavailable"). The key was not deleted and still authenticates; retry the request.
Authentication Flow (Developer API Keys)
API consumers authenticate by sending both the X-API-Key and X-API-Secret
headers on every request. The observable behavior is:
Valid key and secret: the request is authenticated and processed.
Missing, unknown, or revoked credentials: HTTP 401 with
"code": "invalid_credentials". Revoked keys stop authenticating immediately.Credentials could not be evaluated because the service is temporarily unavailable: HTTP 503 with
"code": "api_key_store_unavailable". Your credentials were never checked, so this is not a reason to rotate them — retry with backoff.
A rejected or revoked Developer API key never carries session_expired, so an
authenticated app session remains valid. The same 503 applies on
/tradingdata when X-API-Key / X-API-Secret are supplied: an unavailable
key store is reported as an outage, never silently downgraded to a guest
(unauthenticated) session.
Limits: 1 Developer API key per user. Revoking your key frees the slot immediately so you can issue a replacement.
Economic News
Get Economic News
POST /user?request_type=economic_news
Free for every authenticated user — no credit cost and no plan requirement.
Field |
Type |
Required |
Description |
|---|---|---|---|
|
number |
No |
Max items per page (default 20, max 50) |
|
string |
No |
Opaque pagination cursor ( |
|
string |
No |
Alias for |
Example body:
{
"limit": 20,
"cursor": "ZXlKaF..."
}
Response:
{
"ok": true,
"data": {
"news": [
{
"id": "a3b2c1d4e5f6",
"type": "news",
"title": "Fed signals steady policy outlook amid cooling inflation",
"summary": "FOMC members emphasized balanced risk assessment...",
"source": "bloomberg",
"url": "https://...",
"sentiment": "MILDLY_BULLISH",
"sentiment_label": "MILDLY_BULLISH",
"sentiment_score": 2,
"impact": "HIGH",
"category": "FED",
"affected_coins": ["BTC", "ETH"],
"affected_markets": ["CRYPTO", "EQUITY"],
"country": "US",
"published_at": "2026-08-30T13:30:00+00:00",
"timestamp": 1788096600
}
],
"has_more": true,
"next_cursor": "ZXlKaF...",
"last_evaluated_key": "ZXlKaF...",
"cost_credits": 0.0
},
"timestamp": 1788097000
}
Errors: 400 if invalid cursor.
Get Economic Calendar
POST /user?request_type=economic_calendar
Free for every authenticated user — no credit cost and no plan requirement.
Field |
Type |
Required |
Description |
|---|---|---|---|
|
number |
No |
Max items per page (default 20, max 50) |
|
string |
No |
|
|
boolean |
No |
Alternative to |
|
number |
No |
Number of upcoming days to include when |
|
string |
No |
Opaque pagination cursor ( |
|
string |
No |
Alias for |
Example body:
{
"limit": 20,
"mode": "upcoming",
"horizon_days": 7
}
Response:
{
"ok": true,
"data": {
"calendar": [
{
"id": "cal_us_cpi_20260830",
"type": "calendar",
"title": "Core PCE Price Index m/m",
"source": "forexfactory",
"scheduled_date": "2026-08-30T12:30:00Z",
"event_time": "2026-08-30T12:30:00+00:00",
"impact": "HIGH",
"forecast": "0.2%",
"previous": "0.3%",
"country": "US",
"timestamp": 1788093000
}
],
"has_more": true,
"next_cursor": "ZXlKaF...",
"last_evaluated_key": "ZXlKaF...",
"mode": "upcoming",
"cost_credits": 0.0
},
"timestamp": 1788097000
}
Errors: 401 if unauthenticated, 400 if invalid cursor.
Strategy
Create Strategy
POST /user?request_type=strategy_generate
Field |
Type |
Required |
Description |
|---|---|---|---|
|
string |
Yes |
Unique only within the authenticated user’s account; lowercase alphanumeric, no spaces, min 4 chars, max 20 chars, at least 1 letter ( |
|
string |
Yes |
|
|
string |
Yes |
|
|
string |
Yes |
|
|
string |
Yes |
Entry condition in natural language |
|
string |
Yes* |
Take profit (*required for signal_orders) |
|
string |
Yes* |
Stop loss (*required for signal_orders) |
|
string |
Conditional |
|
Note
Strategies are always created as private. Use strategy_visibility to make public after a successful full_range backtest.
Cost: 1 credit, charged upfront. Fully refunded if generation fails, so a failed attempt costs nothing.
Example body:
{"strategy_name": "emacross", "market_type": "spot", "signal_mode": "signal_orders", "timeframe": "4h", "entry_condition": "EMA 9 crosses above EMA 21", "tp_condition": "Price reaches +3%", "sl_condition": "Price drops -2%"}
Response:
{"ok": true, "data": {"strategy_name": "emacross", "version": 1, "signal_mode": "signal_orders", "status": "generating", "cost_upfront": 1.0, "cost_refunded_on_failure": 1.0}}
Errors: 400 if strategy_name is invalid (too short, too long, or contains invalid characters); 400 if position_side is required but missing ("position_side is required for futures signal_orders strategies"); 400 if position_side value is invalid ("position_side must be 'long' or 'short'"); 402 if insufficient credits; 409 if the authenticated user already has a strategy with the same name; 429 with reason: "strategy_generation_in_progress" (plus Retry-After) if you already have the maximum number of generations running — wait for one to finish, then retry. See the Common reason codes table.
Get Strategy Detail
POST /user?request_type=strategy_detail
Field |
Type |
Required |
Description |
|---|---|---|---|
|
string |
Yes |
Strategy owned by the authenticated user |
Only safe editable/display fields are returned. Strategy source code, owner credentials and private metadata are not exposed.
Response:
{
"ok": true,
"data": {
"strategy_name": "emacross",
"status": "active",
"version": 3,
"market_type": "spot",
"signal_mode": "signal_orders",
"timeframe": "4h",
"entry_condition": "EMA 12 crosses above EMA 26",
"tp_condition": "Price reaches +5%",
"sl_condition": "Price drops -3%",
"visibility": "private",
"credits_per_signal": 0,
"public_coins": [],
"candle_count": 32,
"created_at": 1784000000,
"updated_at": 1784900000
}
}
Errors: 400 missing strategy name, 404 not found. Strategies are looked up scoped to the authenticated owner, so a strategy you do not own is reported as 404 (not 403).
Edit Strategy
POST /user?request_type=strategy_edit
Field |
Type |
Required |
Description |
|---|---|---|---|
|
string |
Yes |
Existing strategy you own |
|
string |
No |
New entry condition |
|
string |
No |
New take profit |
|
string |
No |
New stop loss |
Warning
Editing a strategy creates a new version and triggers AI code regeneration. All marketplace subscribers of this strategy will be automatically unsubscribed.
Example body:
{"strategy_name": "emacross", "entry_condition": "EMA 12 crosses above EMA 26", "tp_condition": "Price reaches +5%", "sl_condition": "Price drops -3%"}
Response:
{"ok": true, "data": {"strategy_name": "emacross", "version": 3, "status": "generating", "copiers_cancelled": true}}
Set Strategy Visibility
POST /user?request_type=strategy_visibility
Field |
Type |
Required |
Description |
|---|---|---|---|
|
string |
Yes |
|
|
string |
Yes |
|
|
number |
Yes* |
0.01-10 (*required for public) — per-signal fee in credits, charged to a subscriber each time they receive a signal from this strategy |
|
string |
Yes* |
(*required for public) e.g. |
|
string[] |
Yes* |
(*required for public) coins to list |
Warning
Requirements for public: Must have a successful full_range backtest. Only coins with total_return > 0% are eligible.
Example body (make public):
{"strategy_name": "emacross", "visibility": "public", "credits_per_signal": 0.5, "timeframe": "4h", "coins": ["BTC", "ETH", "SOL"]}
Response (public):
{"ok": true, "data": {"strategy_name": "emacross", "visibility": "public", "credits_per_signal": 0.5, "timeframe": "4h", "eligible_coins": ["BTC", "ETH"], "rejected_coins": {"SOL": "negative_return"}}}
Example body (make private):
{"strategy_name": "emacross", "visibility": "private"}
Response (private):
{"ok": true, "data": {"strategy_name": "emacross", "visibility": "private"}}
Errors: 400 no full_range backtest, 400 no coins with positive return, 400 missing required fields for public.
How fees work: When a subscriber receives a signal from your public strategy, credits_per_signal is deducted from their balance and credited to you (minus platform fee).
Get Strategy Versions
POST /user?request_type=strategy_versions
Field |
Type |
Required |
|---|---|---|
|
string |
Yes |
Response:
{"ok": true, "data": {"strategy_name": "emacross", "current_version": 3, "versions": [{"version": 1, "created_at": 1784000000}, {"version": 2, "created_at": 1784500000}, {"version": 3, "created_at": 1784900000}]}}
Rollback Strategy
POST /user?request_type=strategy_rollback
Field |
Type |
Required |
|---|---|---|
|
string |
Yes |
|
number |
Yes |
Danger
Rollback sets visibility to private, resets credits_per_signal to 0, and cancels all marketplace subscriptions.
Example body:
{"strategy_name": "emacross", "target_version": 2}
Response:
{"ok": true, "data": {"strategy_name": "emacross", "new_version": 4, "rolled_back_to": 2, "visibility": "private", "credits_per_signal": 0, "marketplace_subscriptions_cancelled": 3}}
Delete Strategy
POST /user?request_type=strategy_delete
Field |
Type |
Required |
Description |
|---|---|---|---|
|
string |
Yes |
Strategy owned by the authenticated user |
|
string |
Yes |
Must exactly equal |
Danger
This is destructive. The client must require the user to type the exact strategy name. A strategy with an active marketplace listing cannot be deleted; unpublish it first.
The operation verifies ownership and marketplace state before deletion. If the request cannot be completed safely, no user-visible strategy data is removed.
Response:
{"ok": true, "data": {"strategy_name": "emacross", "deleted": true, "deleted_objects": 8}}
Errors:
400: missing name or confirmation mismatch
404: strategy not found (a strategy you do not own is also reported as 404, because the lookup is scoped to the authenticated owner)
409: active marketplace listing must be unpublished first
503: the deletion could not be completed safely; nothing was deleted
Backtest
Endpoint: POST /backtest?action=<action>
Two backtest modes available:
Specified Range (
action=run): Test a strategy on a specific date range for a single instrument.Full Range (
action=full_range): Test a strategy on all available data for multiple instruments. Required for marketplace publishing.
Backtest requests remain backward compatible with the original crypto fields:
coin and coins still mean Binance-style crypto symbols such as BTC.
For non-crypto datasets, send instrument metadata alongside the legacy field:
symbol (for example AAPL, EURUSD, GOLD), asset_class
(crypto, stock, etf, forex, index, commodity), provider,
market, and optionally exchange. The service reads partitioned candles from
the matching DATAS/{asset_class}/{market}/{symbol}/{timeframe}/ for crypto and DATAS/{market}/{symbol}/{timeframe}/ for non-crypto dataset when present and
falls back to the legacy DATAS/{COIN}USDT_{TIMEFRAME}.txt files for crypto.
Yahoo-backed non-crypto datasets currently use simple app symbols and daily
data: us_stocks (AAPL, NVDA, TSLA, MSFT, AMZN, META), etfs
(SPY, QQQ), forex (EURUSD, GBPUSD, USDJPY), and commodities
(GOLD, SILVER, OIL). Provider-specific symbols such as GC=F are kept
resolved server-side and never appear in the symbols this API accepts or returns.
Estimate Cost
POST /backtest?action=estimate
Field |
Type |
Required |
|---|---|---|
|
string |
Yes |
|
string[] |
Yes |
|
string |
Yes |
|
string |
No |
|
string |
No |
|
string |
No |
|
string |
No |
|
string |
No |
Response:
{"action": "estimate", "total_candles": 6527547, "estimated_cost_credits": 0.35, "estimated_duration_seconds": 31, "chunks": 14, "current_credits": 7.35, "can_afford": true}
Get Instrument Backtest Info
POST /backtest?action=info
Field |
Type |
Required |
|---|---|---|
|
string |
Yes |
|
string |
Yes* |
|
string |
No |
|
string |
Yes |
|
string |
No |
Response:
{"action": "info", "coin": "BTC", "symbol": "BTCUSDT", "instrument": {"asset_class": "crypto", "provider": "binance", "market": "spot", "symbol": "BTCUSDT"}, "timeframe": "4h", "data_range": {"first_ts": 1514764800, "last_ts": 1784476800, "first_date": "2018-01-01", "last_date": "2026-07-19", "total_candles": 21837}, "strategy": {"slug": "emacross", "candle_count": 32}, "cost_preview": {"credits_if_full": 0.05}, "current_credits": 7.35}
List Available Timeframes
POST /backtest?action=list_timeframes
Field |
Type |
Required |
|---|---|---|
|
string |
Yes* |
|
string |
No |
|
string |
No |
Response:
{"action": "list_timeframes", "coin": "AAPL", "symbol": "AAPL", "instrument": {"asset_class": "stock", "provider": "yahoo", "market": "us_stocks", "symbol": "AAPL"}, "timeframes": [{"timeframe": "1d", "suffix": "1DAY", "s3_key": "DATAS/us_stocks/AAPL/1d/manifest.json"}]}
Run Specified Range Backtest
POST /backtest?action=run
Field |
Type |
Required |
Default |
Description |
|---|---|---|---|---|
|
string |
Yes |
Strategy to test |
|
|
string |
Yes* |
Backward-compatible display symbol, e.g. |
|
|
string |
No |
Tradable/data symbol, e.g. |
|
|
string |
No |
|
|
|
string |
No |
|
Data provider namespace |
|
string |
No |
|
Dataset market namespace |
|
string |
No |
provider |
Venue label |
|
string |
Yes |
e.g. |
|
|
number |
Yes |
Start unix timestamp |
|
|
number |
Yes |
End unix timestamp |
|
|
number |
No |
0.2 |
Commission % |
|
number |
No |
100 |
Position size % |
|
number |
No |
4 |
Min candles between signals |
|
number |
No |
30 |
Max days to wait for fill |
|
number |
No |
0.1 |
Min profit target % |
|
number |
No |
30 |
Max profit target % |
|
number |
No |
0.1 |
Min stop loss % |
|
number |
No |
50 |
Max stop loss % |
|
string |
No |
|
|
Notes:
Spread is automatically determined by crypto coin volume tier for legacy Binance crypto. Non-crypto datasets currently use the default low-volume fallback unless the backend is extended with asset-class-specific spread rules.
signal_filter_mode = “clamp” (default): If strategy TP exceeds
max_profit_pct, it’s clamped to max. If SL exceedsmax_loss_pct, clamped to max. Signals belowmin_profit_pctare always skipped.signal_filter_mode = “reject”: Signals that exceed any min/max limit are completely skipped.
min/max profit/loss values are passed to the strategy function as ratio parameters (
min_profit_ratio,max_profit_ratio,max_loss_ratio,min_loss_ratio) for TP/SL price calculation.
Response:
{"action": "run", "mode": "async_chunked", "job_id": "1784936800_6a2326", "cost_credits": 0.05, "remaining_credits": 7.30, "chunks": 1, "status": "dispatched", "poll_actions": {"status": "?action=status&job_id=1784936800_6a2326", "result": "?action=result&job_id=1784936800_6a2326"}}
Full Range Backtest (for Marketplace)
POST /backtest?action=full_range
Field |
Type |
Required |
|---|---|---|
|
string |
Yes |
|
string[] |
Yes |
|
string |
Yes |
|
string |
No |
Each requested instrument runs as a separate chunk. Required for marketplace_publish with signal_mode=full.
Example body:
{"strategy_name": "emacross", "coins": ["BTC", "ETH", "SOL"], "timeframe": "4h"}
Response:
{"action": "full_range", "job_id": "1784936800_fr_abc", "coins": ["BTC", "ETH", "SOL"], "chunks": 3, "cost_credits": 0.15, "remaining_credits": 7.20, "status": "dispatched", "poll_actions": {"status": "?action=status&job_id=1784936800_fr_abc", "result": "?action=result&job_id=1784936800_fr_abc"}}
Launch Portfolio Backtest
POST /backtest?action=portfolio
Simulates all selected coins against one shared balance. The balance is
split into divide position slots: each position uses
current balance / divide, at most divide positions are open at once, and
at most one position per coin at a time. Signals that arrive with no free
slot are skipped (and counted). Commission and per-coin spread are applied
exactly as in single-coin runs.
Field |
Type |
Required |
Description |
|---|---|---|---|
|
string |
Yes |
|
|
string[] |
Yes |
2–50 legacy coin/instrument labels |
|
string |
No |
Shared dataset namespace for the selected instruments |
|
string |
Yes |
|
|
integer |
No |
1–20; default = number of coins (capped at 20) |
|
number |
No |
Default 100 |
|
number |
No |
Percent, default 0.2 |
|
integer |
No |
Unix seconds; omitted = full history. Each coin is clipped to its own available range |
Example body:
{"strategy_name": "emacross", "coins": ["BTC", "ETH", "SOL"], "timeframe": "4h", "divide": 3, "initial_balance": 100}
Response:
{"action": "portfolio", "job_id": "pf_1784936800_ab12cd", "coins": ["BTC", "ETH", "SOL"], "divide": 3, "initial_balance": 100, "chunks": 3, "cost_credits": 0.25, "status": "dispatched", "poll_actions": {"status": "?action=status&job_id=pf_1784936800_ab12cd", "result": "?action=result&job_id=pf_1784936800_ab12cd"}}
The portfolio result (fetched with action=result) contains, in addition to
the standard summary fields: initial_balance, final_balance, buy_count,
sell_count, avg_stop_loss_pct, avg_target_pct, skipped_no_slot,
skipped_coin_busy, max_concurrent_positions; plus top-level
coin_results (per-coin summaries), per_coin_contribution (dollar PnL by
coin), trades (coin-tagged, first 500, trades_truncated flag) and
recommended_divide:
"recommended_divide": {
"divide": 5, "final_balance": 214.2, "total_return_pct": 114.2,
"max_drawdown_pct": 18.3, "max_concurrent_signals": 9,
"candidates": [{"divide": 1, "total_return_pct": 80.1, "max_drawdown_pct": 31.0, "final_balance": 180.1, "total_trades": 42, "skipped_no_slot": 12}],
"method": "return_dd_score"
}
The recommendation simulates a fixed candidate set of divide values over the
same signal timeline and picks the one maximizing
final_balance × (1 − max_drawdown/200) — return lightly penalized by
drawdown.
Backtest Launch Limits
Launching a backtest (action=run, full_range, or portfolio) is a heavy,
asynchronous operation. To keep the platform fair when many people run jobs at
once, each account has a limit on how many backtest jobs it can have running
at the same time. This limit is not a fixed plan tier — it adapts to current
platform load, so it is higher when the platform is idle and lower when it is
busy.
When you exceed it, the launch is rejected with HTTP 429 and
reason: "backtest_concurrency_limit":
{
"error": "You already have the maximum number of backtests running. Wait for one to finish, then try again.",
"reason": "backtest_concurrency_limit",
"current_running": 4,
"max_concurrent": 4,
"retry_after": 15
}
A Retry-After header accompanies the response. This limit applies only to
the three launch actions. Polling action=status, fetching action=result,
and every other backtest action are never rejected for this reason — a job you
already started always remains pollable to completion. Accepted launches keep
returning "status": "dispatched" as before. See the
Common reason codes table for the full contract and
the related backtest_capacity_exceeded and strategy_generation_in_progress
reasons.
Get Per-Coin Portfolio Result
POST /backtest?action=coin_result
Returns the standalone backtest result of a single coin inside a completed
portfolio job (same shape as a single-coin result: summary, trades).
Field |
Type |
Required |
|---|---|---|
|
string |
Yes |
|
string |
Yes |
Poll Status
POST /backtest?action=status
Field |
Type |
Required |
|---|---|---|
|
string |
Yes |
Response (running):
{"job_id": "...", "status": "running", "progress_pct": 65, "elapsed_seconds": 42, "estimated_remaining_seconds": 22}
Response (failed):
{"job_id": "...", "status": "failed", "progress_pct": 65, "elapsed_seconds": 42, "estimated_remaining_seconds": 0, "error": "Backtest execution failed"}
Failed-job credit recovery is automatic and is not a customer endpoint. The status response contains only the customer-visible job state.
Status values: running, completed, failed
Get Result
POST /backtest?action=result
Field |
Type |
Required |
|---|---|---|
|
string |
Yes |
Response:
{
"action": "run", "job_id": "...", "coin": "BTC", "timeframe": "4h",
"summary": {
"total_signals": 15, "gain_count": 5, "loss_count": 2, "total_trades": 7,
"win_rate": 71.43, "final_balance": 105.46, "total_return_pct": 5.46,
"max_drawdown_pct": 10.45, "profit_factor": 1.38, "avg_profit_per_trade_pct": 0.76,
"avg_signals_per_month": 0.79, "avg_win_return_pct": 4.97, "avg_loss_return_pct": -9.02,
"best_trade_pct": 5.33, "worst_trade_pct": -10.45
},
"all_positions": [], "trades": [],
"total_positions": 15, "total_trades": 7, "cost_credits": 0.05
}
Delete Backtest
POST /backtest?action=delete_backtest
Field |
Type |
Required |
|---|---|---|
|
string |
Conditional |
|
string |
Conditional |
|
string |
Conditional |
Response:
{"action": "delete_backtest", "success": true, "job_id": "1784936800_6a2326", "deleted_objects": 6}
The legacy {strategy_name, backtest_key} request remains backward compatible.
Backtest History
POST /user?request_type=backtest_history
Field |
Type |
Required |
Description |
|---|---|---|---|
|
string |
No |
Filter by strategy |
|
string |
No |
Filter by coin (e.g. |
|
string |
No |
Filter by timeframe (e.g. |
|
integer |
No |
Default 25, min 1, max 100 |
|
string |
No |
Opaque cursor from the previous filtered page |
Example body:
{"strategy_name": "emacross", "coin": "BTC", "timeframe": "4h"}
Response:
{"ok": true, "data": {"backtests": [{"job_id": "...", "strategy_name": "emacross", "coin": "BTC", "timeframe": "4h", "cost_credits": 0.05, "created_at": 1784936800, "summary": {"total_trades": 7, "win_rate": 71.43, "total_return_pct": 5.46}}], "count": 1, "last_evaluated_key": "base64...", "has_more": true}}
Filtering is applied before pagination. The cursor represents the last
(created_at, job_id) pair and must be returned unchanged. Legacy job IDs
recover their timestamp when created_at is absent. Malformed cursors and
non-integer page sizes return HTTP 400.
Signals
Active Signals
POST /user?request_type=active_signals
Field |
Type |
Required |
Description |
|---|---|---|---|
|
string or string[] |
No |
Filter by strategy. Single string or array. Omit for all signals. |
|
integer |
No |
Default 25, min 1, max 50 |
|
string |
No |
Opaque cursor returned by the previous page |
Example body (single strategy):
{"strategy_name": "emacross"}
Example body (multiple strategies):
{"strategy_name": ["emacross", "rsibounce"]}
Example body (all signals):
{}
Response:
{"ok": true, "data": {"signals": [{"position_key": "emacross#BTC#1784850000", "coin": "BTC", "strategy_name": "emacross", "owner": "MTHG7A", "side": "LONG", "signal_mode": "signal_orders", "entry_price": "67234.50", "limit_price": "69500.00", "stop_price": "65800.00", "source": "user", "created_at": 1784850000}], "count": 1, "last_evaluated_key": "base64...", "has_more": true}}
Return the opaque cursor unchanged. Malformed cursors and non-integer page sizes return HTTP 400.
Signal History
POST /user?request_type=signal_history
Field |
Type |
Required |
Description |
|---|---|---|---|
|
object |
No |
Filter object (see below) |
|
number |
No |
Default 20, max 50 |
|
string |
No |
Pagination cursor (base64) |
Filter object fields:
Field |
Type |
Description |
|---|---|---|
|
string |
Filter by coin |
|
string |
Filter by strategy |
|
string |
Filter by signal owner |
Example body:
{"filters": {"coin": "BTC", "strategy_name": "emacross"}, "page_size": 20}
Response:
{"ok": true, "data": {"signals": [{"closed_key": "...", "coin": "BTC", "strategy_name": "emacross", "side": "LONG", "signal_mode": "signal_orders", "entry_price": "67234.50", "limit_price": "69500.00", "stop_price": "65800.00", "exit_price": "69500.00", "result": "GAIN", "source": "user", "created_at": 1784850000, "closed_at": 1784950000}], "count": 1, "last_evaluated_key": "base64...", "has_more": true}}
Return the opaque cursor unchanged. Malformed cursors and non-integer page
sizes return HTTP 400. Filters are evaluated on each result page; clients
must continue while has_more is true even if a filtered page is empty.
Marketplace
Browse Listings
GET /tradingdata?request_type=marketplace_browse&limit=50
Guest-accessible: this endpoint may be called without authentication, subject to stricter IP-based rate limits. When credentials are supplied the caller identity is tracked for rate limiting, audit and entitlement checks.
Query parameter |
Type |
Required |
Description |
|---|---|---|---|
|
string |
|
|
|
string |
e.g. |
|
|
string |
Filter by one listed coin |
|
|
number |
Default 50, min 1, max 100 |
|
|
string |
Opaque cursor returned by the previous page |
Response:
{"listings": [{"listing_id": "lst_abc123", "strategy_name": "emacross", "owner": "MT***A", "market_type": "spot", "timeframe": "4h", "signal_mode": "full", "listed_coins": ["BTC", "ETH"], "billing_period": "monthly", "monthly_price_credits": 20, "total_pnl_pct": 12.5, "win_rate_pct": 68.0, "subscriber_count": 5, "total_signals_delivered": 42}], "next_cursor": "base64...", "has_more": true}
Send the returned cursor unchanged to fetch the next page. has_more=false
and a null cursor identify the final page. Malformed cursors and non-integer
page sizes return HTTP 400 instead of silently restarting at page one.
Listing Detail
GET /tradingdata?request_type=marketplace_listing&listing_id=lst_abc123
Guest-accessible (stricter IP-based rate limits when unauthenticated). The response masks unrelated owner data and returns caller-specific ownership and subscription state when the caller is authenticated.
For the authenticated caller’s ownership and subscription state, use:
POST /user?request_type=marketplace_listing_detail
Field |
Type |
Required |
|---|---|---|
|
string |
Yes |
Response:
{
"ok": true,
"data": {
"listing_id": "lst_abc123",
"strategy_name": "emacross",
"owner": "MT***A",
"description": "EMA crossover strategy",
"market_type": "spot",
"timeframe": "4h",
"leverage": 1,
"signal_mode": "full",
"listed_coins": ["BTC", "ETH"],
"billing_period": "monthly",
"monthly_price_credits": 20,
"total_pnl_pct": 12.5,
"win_rate_pct": 68.0,
"subscriber_count": 5,
"total_signals_delivered": 42,
"created_at": 1784000000,
"is_owner": false,
"subscription_status": "active",
"selected_coins": ["BTC"]
}
}
is_owner is evaluated for the authenticated caller. When that caller has a
deterministic subscription for the listing, subscription_status and
selected_coins describe it; otherwise they are an empty string and list.
Errors: 404 listing not found (or removed and not owner).
Subscribe
POST /user?request_type=marketplace_subscribe
Field |
Type |
Required |
Description |
|---|---|---|---|
|
string |
Yes |
|
|
string[] |
Yes |
Must be subset of listed_coins |
Validations: Valid license required. Cannot self-subscribe. Plan coin/strategy limits apply.
Example body:
{"listing_id": "lst_abc123", "selected_coins": ["BTC", "ETH"]}
Response:
{"ok": true, "data": {"subscription_id": "sub_5c9f...", "listing_id": "lst_abc123", "strategy_name": "emacross", "active_coins": ["BTC", "ETH"], "billing_period": "monthly", "monthly_price_credits": 20, "credits_charged": 20, "next_charge_at": 1786600000, "expires_at": 1786600000, "bindings_created": 2}}
The subscription ID is deterministic for the authenticated user and listing. The subscription is all-or-nothing; a failed request leaves no partial subscription.
Errors: 400 invalid coins / coin limit / self-subscribe / listing not active, 402 insufficient credits ("code": "insufficient_credits" — your balance is below the listing’s monthly_price_credits), 403 license expired or active-strategy plan limit, 404 listing not found, 409 duplicate subscription or binding conflict, 503 atomic commit unavailable.
Unsubscribe
POST /user?request_type=marketplace_unsubscribe
Field |
Type |
Required |
|---|---|---|
|
string |
Yes |
Response:
{"ok": true, "data": {"status": "cancelled", "subscription_id": "sub_xyz789", "bindings_removed": 2}}
My Listings
POST /user?request_type=marketplace_my_listings
Field |
Type |
Required |
Description |
|---|---|---|---|
|
integer |
No |
Default 25, min 1, max 100 |
|
string |
No |
Opaque cursor returned by the previous page |
Response:
{"ok": true, "data": {"listings": [{"listing_id": "lst_abc123", "strategy_name": "emacross", "status": "active", "signal_mode": "full", "market_type": "spot", "timeframe": "4h", "listed_coins": ["BTC"], "billing_period": "monthly", "monthly_price_credits": 20, "subscriber_count": 5, "total_signals_delivered": 42, "total_credits_earned": 100.0, "total_pnl_pct": 12.5, "win_rate_pct": 68.0, "created_at": 1784000000}], "last_evaluated_key": "base64...", "has_more": true}}
My Subscriptions
POST /user?request_type=marketplace_my_subscriptions
Field |
Type |
Required |
Description |
|---|---|---|---|
|
integer |
No |
Default 25, min 1, max 100 |
|
string |
No |
Opaque cursor returned by the previous page |
Response:
{"ok": true, "data": {"subscriptions": [{"subscription_id": "sub_xyz789", "listing_id": "lst_abc123", "creator_beyin_id": "ABC123", "selected_coins": ["BTC"], "status": "active", "billing_period": "monthly", "monthly_price_credits": 20, "signals_received": 12, "credits_spent": 20.0, "next_charge_at": 1786600000, "expires_at": 1786600000, "created_at": 1784000000}], "last_evaluated_key": "base64...", "has_more": true}}
For both account lists, return the cursor unchanged to fetch the next result page. Malformed cursors and non-integer page sizes return HTTP 400.
Publish Strategy
POST /user?request_type=marketplace_publish
Field |
Type |
Required |
Description |
|---|---|---|---|
|
string |
Yes |
Lowercase letters and numbers only |
|
string |
No |
Max 500 chars, no HTML |
|
number |
Yes |
1 - 1000 — fixed MONTHLY subscription fee in credits (billing is always monthly, prepaid) |
|
string |
No |
|
|
string[] |
Yes |
Non-empty list of coins to list |
Warning
Requirements for signal_mode=full: Must have a successful full_range backtest. Only coins with positive PnL and at least 10 trades are listed; others are rejected (negative_pnl, insufficient_trades, or no_data). signal_mode=signal_only requires no backtest and lists every requested coin.
Marketplace listing names are globally disambiguated by appending the creator’s
Beyin ID to the user’s local strategy name. For example, a local strategy named
test1 owned by ABC123 is listed as test1_ABC123. Other users may still
create their own local test1 strategy.
Example body:
{"strategy_key": "emacross", "description": "EMA crossover for BTC", "monthly_price_credits": 20, "signal_mode": "full", "requested_coins": ["BTC", "ETH", "SOL"]}
Response:
{"ok": true, "data": {"listing_id": "lst_abc123", "strategy_key": "emacross_ABC123", "strategy_name": "emacross_ABC123", "display_strategy_name": "emacross", "status": "active", "listed_coins": ["BTC", "ETH"], "rejected_coins": {"SOL": "negative_pnl", "DOGE": "insufficient_trades"}, "monthly_price_credits": 20, "billing_period": "monthly", "signal_mode": "full", "backtest_summary": {"BTC": {"pnl_pct": 12.5, "win_rate": 68.0, "trades": 42}}}}
Errors: 400 invalid strategy_key, 400 invalid signal_mode, 400 monthly_price_credits out of range (1-1000), 400 empty requested_coins, 400 HTML in description, 400 no full_range backtest found (full mode), 400 no profitable coins, 404 strategy not found.
Update Listing
POST /user?request_type=marketplace_update_listing
Field |
Type |
Required |
Description |
|---|---|---|---|
|
string |
Yes |
|
|
number |
No |
1 - 1000 — new monthly subscription fee |
|
string |
No |
Max 500 chars, no HTML |
Danger
Changing the price cancels ALL active subscriptions and removes their bindings.
Example body:
{"listing_id": "lst_abc123", "monthly_price_credits": 30, "description": "Updated description"}
Response:
{"ok": true, "data": {"listing_id": "lst_abc123", "updated_fields": ["monthly_price_credits"], "subscriptions_cancelled": 3, "note": "Price changed 20 -> 30. All subscriptions cancelled."}}
Errors: 400 nothing to update / invalid price / HTML in description, 400 listing not active, 403 not your listing, 404 listing not found.
Unpublish Listing
POST /user?request_type=marketplace_unpublish
Field |
Type |
Required |
|---|---|---|
|
string |
Yes |
Warning
Cancels all subscriptions, removes bindings, sets status to “removed”.
Response:
{"ok": true, "data": {"status": "removed", "subscriptions_cancelled": 5, "bindings_removed": 12}}
Submit Review
POST /user?request_type=marketplace_review
Field |
Type |
Required |
Description |
|---|---|---|---|
|
string |
Yes |
|
|
number |
Yes |
1-5 |
|
string |
No |
Max 500 chars |
Note
Must have (or had) a subscription to review. One review per user per listing. Cannot review own listing. If a comment is supplied it must be at least 10 characters and may not contain URLs (http://, https://, or www.); disallowed markup is rejected.
Errors: 400 rating out of range / comment too short / URL in comment / disallowed content, 403 not a subscriber, 404 listing not found, 400 cannot review own listing.
Example body:
{"listing_id": "lst_abc123", "rating": 4, "comment": "Great strategy, consistent returns!"}
Response:
{"ok": true, "data": {"listing_id": "lst_abc123", "rating": 4, "avg_rating": 4.2, "review_count": 8}}
Get Reviews
GET /tradingdata?request_type=marketplace_reviews&listing_id=lst_abc123&limit=20
Guest-accessible: callable without authentication, subject to stricter IP-based rate limits. When credentials are supplied the caller identity is tracked for rate limiting, audit and entitlement checks.
Query parameter |
Type |
Required |
|---|---|---|
|
string |
Yes |
|
number |
No |
|
string |
No |
Response:
{"listing_id": "lst_abc123", "reviews": [{"review_id": "7c6c...", "rating": 5, "comment": "Great strategy!", "timestamp": 1784900000}], "count": 1, "next_cursor": "base64...", "has_more": true}
review_id is a deterministic, privacy-safe hash; the source account identifier used
for duplicate prevention is never returned. Send the cursor unchanged for the
next page. Malformed cursors and non-integer limits return HTTP 400.
Trading Data
Most Trading Data requests accept an authenticated caller, but a subset is
guest-accessible (callable without credentials, under stricter IP-based
rate limits) — see the guest list in the Authentication
section. Endpoints not on that list require a tracked caller identity and
return HTTP 401 without one. Some responses may still be license-gated by plan
after the caller is identified (for example, trend_signals pages beyond the
first).
Trend Signals
GET /tradingdata?request_type=trend_signals&page=0&limit=50
Param |
Type |
Required |
Description |
|---|---|---|---|
|
number |
No |
Page number (default 0). Page > 0 requires license. |
|
number |
No |
Items per page (default 50, max 50; values above 50 are clamped) |
Response:
{"items": [{"coin_name": "BTC", "graph_type": "4h", "timestamp": "1784900000", "way": "BUY", "is_return_to_trend": false}], "page": 0, "count": 10, "last_page": false}
Collection items are lightweight discovery records. Clients must not rely on
klines_data in this response; request the selected signal through
trend_signal_detail when rendering its chart.
Trend Signal Detail
GET /tradingdata?request_type=trend_signal_detail&coin_name=BTC&graph_type=240×tamp=1784900000
Param |
Type |
Required |
Description |
|---|---|---|---|
|
string |
Yes |
e.g. |
|
string |
Yes |
Signal timeframe exactly as returned by the list. It may be a formatted value such as |
|
string |
Yes |
Signal timestamp |
Response:
{"item": {"coin_name": "BTC", "graph_type": "4h", "timestamp": "1784900000", "way": "BUY", "is_return_to_trend": false, "trend_data": {"low_trend": [["1784800000000", 12, "67200.5"], ["1784900000000", 90, "68120.0"]]}, "klines_data": []}}
trend_data contains the calculated high/low trend-line anchor points. Each
point is [timestamp_ms, candle_index, price]; the two values are anchors,
not candles. The client draws the line from anchor 1 through anchor 2 and
stops it at the break candle.
klines_data is the signal snapshot used to render the historical chart. A
detail response intended for the Trend Break history UI must include the full
signal snapshot returned by the API. Storage-only fields and internal links are
not part of the public response contract.
Market Ticker
GET /tradingdata?request_type=market_ticker&market=spot&page=0&limit=500
Returns the market list used by the official app. Clients must call this Beyin Finance API endpoint; they must not call exchange ticker endpoints directly. The API filters ticker rows through server-maintained exchange metadata so only symbols currently enabled for trading in the selected market are returned.
Price and quantity precision metadata may exist server-side for symbols that are not currently trading, but those symbols are excluded from this ticker catalog until they become trading-enabled again.
Instrument precision is maintained in one compact server manifest:
INSTRUMENTS/instrument_manifest_v1.json. The file is grouped by market so
repeated fields such as asset_class, provider, exchange, market, and
quote_asset are stored once per market group. Each symbol only stores values
that differ from that group’s defaults. Backtest, trading precision checks,
and multi-market routing use this manifest as the canonical source.
Example manifest shape:
{
"version": 1,
"updated_at": "2026-08-13T12:00:00Z",
"markets": {
"crypto_binance_spot_usdt": {
"asset_class": "crypto",
"provider": "binance",
"exchange": "BINANCE",
"market": "spot",
"quote_asset": "USDT",
"defaults": {},
"symbols": {
"BTC": {
"price_precision": 2,
"quantity_precision": 5,
"tick_size": "0.01",
"step_size": "0.00001",
"min_quantity": "0.00001",
"min_notional": "5"
}
}
},
"us_stocks_yahoo_us_usd": {
"asset_class": "stock",
"provider": "yahoo",
"exchange": "US",
"market": "us_stocks",
"quote_asset": "USD",
"defaults": {
"price_precision": 2,
"quantity_precision": 6,
"tick_size": "0.01",
"step_size": "0.000001",
"min_quantity": "0.000001",
"min_notional": "1"
},
"symbols": {
"AAPL": {"provider_symbol": "AAPL", "display_name": "Apple"},
"NVDA": {"provider_symbol": "NVDA", "display_name": "Nvidia"}
}
}
}
}
Param |
Type |
Required |
Description |
|---|---|---|---|
|
string |
No |
|
|
number |
No |
Page number (default 0) |
|
number |
No |
Items per page (default 500, max 1000) |
|
string |
No |
Alphanumeric symbol search, e.g. |
|
string |
No |
Comma-separated USDT symbols prioritized first |
Response:
{
"items": [
{
"symbol": "BTCUSDT",
"last_price": "67200.10",
"change_percent_24h": "1.25",
"quote_volume_24h": "123456789.0",
"market_type": "spot",
"price_precision": 2,
"favorite": false
}
],
"page": 0,
"count": 1,
"total": 420,
"last_page": false
}
If live exchange ticker data is temporarily unavailable, the API may return a
recent verified cache and include _cache.stale=true. Clients should show a
stale-data warning when _cache is present.
Market Quote
GET /tradingdata?request_type=market_quote&symbol=BTCUSDT&market=spot
Returns a lightweight live quote for chart order preparation. This endpoint does not submit orders and does not require user Binance credentials. It is guest-accessible (callable without authentication, under stricter IP-based rate limits); when credentials are supplied the caller is tracked.
Param |
Type |
Required |
Description |
|---|---|---|---|
|
string |
Yes |
Binance USDT pair, e.g. |
|
string |
No |
|
Response:
{
"symbol": "BTCUSDT",
"market_type": "spot",
"bid_price": "67200.10",
"ask_price": "67200.20",
"last_price": "67200.15",
"spread": "0.10",
"spread_percent": "0.0001488",
"commission_rate": "0.001",
"commission_percent": "0.1",
"timestamp": 1784900000000
}
For market=futures, the response also includes:
{
"mark_price": "67200.12",
"funding_rate": "0.0001",
"next_funding_time": "1784908800000"
}
Clients should refresh this endpoint at a low frequency suitable for UI previews, currently 5 seconds in the mobile chart. Any real order preview or submission must revalidate bid/ask, funding, commission, precision, notional, and risk limits server-side.
Market Sentiment
GET /tradingdata?request_type=trend_indicator&market_key=BTCUSDT%231h
Param |
Type |
Required |
Description |
|---|---|---|---|
|
string |
No |
Format: |
Response:
{"market_key": "BTCUSDT#1h", "item": {"market_key": "BTCUSDT#1h", "timestamp": 1784900000, "score": 72, "direction": "BULLISH"}, "count": 1}
The product fields are score (integer sentiment score) and direction
(e.g. "BULLISH" / "BEARISH" / "NEUTRAL"). The item object may carry
additional internal fields; treat any field not documented here as
unstable and ignore it — do not depend on its presence, name, or value.
Sentiment History
GET /tradingdata?request_type=trend_indicator_history&market_key=BTCUSDT%231h&limit=50
Param |
Type |
Required |
Description |
|---|---|---|---|
|
string |
No |
Default: |
|
number |
No |
Max 200 (default 200) |
|
number |
No |
Pagination: get items before this timestamp |
Response:
{"market_key": "BTCUSDT#1h", "items": [{"timestamp": 1784900000, "score": 72, "direction": "BULLISH"}], "count": 50, "limit": 50, "has_more": true, "oldest_timestamp": 1784720000, "next_before_timestamp": 1784720000}
General Config & Platform Data
Get Platform Config
GET /
Public — no authentication required. This is the startup configuration the client fetches before login. Returns platform metadata such as banners and supported assets. Cache locally and ignore unknown fields.
Response:
{
"GENERAL": "general",
"banner_urls": ["https://..."],
"supported_coins": ["BTC", "ETH", "SOL"],
"app_version": "2.0.0"
}
Get Available Coins
POST /user?request_type=available_coins
No body params. Returns coins that are TRADING on Binance AND have kline data available for backtest/signals.
Response:
{"ok": true, "data": {"coins": ["ADA", "AVAX", "BNB", "BTC", "DOGE", "DOT", "ETH", "LINK", "SOL", "XRP"], "count": 285}}
Cache this response; the available set changes infrequently.
Get Platform Notifications
GET /tradingdata?request_type=platform_notifications&limit=20
Guest-accessible (stricter IP-based rate limits when unauthenticated). Returns system announcements such as new features and maintenance messages.
Param |
Type |
Required |
Description |
|---|---|---|---|
|
number |
No |
Default 20, max 50 |
Response:
{"notifications": [{"title": "New Feature", "body": "Marketplace is now live!", "type": "announcement", "timestamp": 1784900000}], "count": 1}
Community
Global Chat - Send Message
POST /user?request_type=community_chat_send
Field |
Type |
Required |
Description |
|---|---|---|---|
|
string |
Yes |
Max 500 characters |
Response:
{"ok": true, "data": {"msg_id": "1784990000_MTHG7A", "sort_key": "1784990000#1784990000_MTHG7A"}}
Global Chat - History
GET /tradingdata?request_type=community_chat&limit=50
Guest-accessible for reading visible community messages (stricter IP-based rate
limits when unauthenticated). Use the returned next_cursor as the cursor
query parameter for the next page.
Query parameter |
Type |
Required |
Description |
|---|---|---|---|
|
number |
No |
Default 50, max 100 |
|
string |
No |
Cursor returned by the previous page |
Response:
{"messages": [{"beyin_id": "MTHG7A", "message": "BTC looking bullish!", "created_at": "1784990000", "sort_key": "..."}], "count": 50, "has_more": true, "next_cursor": "1784980000#..."}
Pass next_cursor back as cursor to request the next page.
The cursor is null and has_more=false on the final page.
Global Chat - Delete Message
POST /user?request_type=community_chat_delete
Soft-deletes a chat message. Two callers are authorized:
a channel moderator (admin, or the leader who owns the channel) may delete any message in that channel,
the author may delete their own message.
Any other caller receives 403. The message row is kept and its text is replaced
with a placeholder (Bu mesaj silindi for an author deletion,
Bu mesaj admin tarafından silindi for a moderator deletion) so thread replies
stay intact.
Field |
Type |
Required |
Description |
|---|---|---|---|
|
string |
Yes |
|
|
string |
No |
Channel identifier, default |
Response:
{"ok": true, "data": {"sort_key": "1784990000#1784990000_MTHG7A", "deleted": true, "deleted_by_author": true}, "timestamp": 1784990100}
Status |
Code |
Meaning |
|---|---|---|
400 |
— |
|
401 |
|
No valid session or API key |
403 |
|
Not a moderator and not the author |
404 |
|
Message does not exist in that channel |
Create Post (Leaders Only)
POST /user?request_type=community_post_create
Only users with community_role: "leader" can create posts.
Field |
Type |
Required |
Description |
|---|---|---|---|
|
string |
No |
Post title |
|
string |
Yes |
Max 5000 characters |
|
string |
No |
HTTPS URL for the attached image |
Response:
{"ok": true, "data": {"post_id": "post_MTHG7A_1784990000"}}
Followers are automatically notified through their configured channels.
List Posts
GET /tradingdata?request_type=community_posts&limit=20
Guest-accessible for reading visible leader posts (stricter IP-based rate
limits when unauthenticated). Pagination uses the opaque next_cursor response
value as the next request’s cursor.
The feed is returned newest-first by created_at. Clients must request bounded
pages instead of loading the entire community feed at once.
Query parameter |
Type |
Required |
Description |
|---|---|---|---|
|
integer |
No |
Default 20, min 1, max 50 |
|
string |
No |
Opaque cursor returned by the previous page |
Response:
{"posts": [{"post_id": "...", "author_id": "MTHG7A", "title": "BTC Analysis", "content": "...", "image_url": "", "like_count": "12", "comment_count": "3", "created_at": "1784990000"}], "count": 20, "has_more": true, "next_cursor": "eyJwb3N0X2lkIjp7IlMiOiIuLi4ifX0="}
The cursor is opaque and must be sent back unchanged. Malformed cursors return HTTP 400 instead of silently restarting at the first page.
Delete Own Post
POST /user?request_type=community_post_delete
Authentication is required. Only the post author can delete the post. Deletion is immediate for readers: the post is marked hidden and no longer appears in Akış or Profilim. The record is retained for moderation/audit for 15 days, then permanently removed.
Field |
Type |
Required |
|---|---|---|
|
string |
Yes |
Response:
{"ok": true, "data": {"post_id": "...", "deleted": true, "delete_after": 1786286000}}
Errors:
400—post_idmissing403— caller is not the post author, or the post is already hidden404— post not found in the table500— unexpected database error (logged server-side, not surfaced raw)
Like Post
POST /user?request_type=community_post_like
Field |
Type |
Required |
|---|---|---|
|
string |
Yes |
Response:
{"ok": true, "data": {"post_id": "...", "action": "liked"}}
Errors: 409 if already liked.
Report Content
POST /user?request_type=community_post_report
Field |
Type |
Required |
Description |
|---|---|---|---|
|
string |
Conditional |
For reporting a post |
|
string |
No |
|
|
string |
Conditional |
For reporting a chat message |
Response:
{"ok": true, "data": {"action": "reported"}}
Follow Leader
POST /user?request_type=community_follow
Field |
Type |
Required |
|---|---|---|
|
string |
Yes |
Response:
{"ok": true, "data": {"leader_id": "MTHG7A", "action": "followed"}}
Unfollow Leader
POST /user?request_type=community_unfollow
Field |
Type |
Required |
|---|---|---|
|
string |
Yes |
Response:
{"ok": true, "data": {"leader_id": "MTHG7A", "action": "unfollowed"}}
List Leaders
GET /tradingdata?request_type=community_leaders&limit=25
Guest-accessible (stricter IP-based rate limits when unauthenticated).
is_following is evaluated for the authenticated caller (and is false for
guests); follow and unfollow actions use the /user endpoints and require
authentication.
Query parameter |
Type |
Required |
Description |
|---|---|---|---|
|
integer |
No |
Default 25, min 1, max 50 |
|
string |
No |
Opaque cursor returned by the previous page |
Response:
{"leaders": [{"beyin_id": "MTHG7A", "name": "CryptoTrader", "bio": "Full-time crypto analyst", "is_following": false}], "count": 1, "next_cursor": "base64...", "has_more": true}
Return the opaque cursor unchanged; malformed cursors return HTTP 400.
Apply for Community Leader
POST /user?request_type=community_leader_apply
Field |
Type |
Required |
Description |
|---|---|---|---|
|
string |
Yes |
Leader Display Name (3-30 Latin alphanumeric chars or spaces, e.g. |
|
string |
Yes |
Reason for application (max 1000 chars) |
|
string |
Yes |
Trading experience details (max 2000 chars) |
The system automatically generates a unique lowercase nickname by removing spaces from display_name (e.g. "Finans Kulubu" -> "finanskulubu"). Returns 409 Conflict if nickname is already taken by another user.
has_500_followers must be true (you confirm you have at least 500 followers).
Optional social links: twitter, instagram, youtube, telegram.
If you already have a pending application, calling this again updates it in place (edit your submission) rather than failing. An approved leader cannot re-apply (409).
Response:
{
"ok": true,
"data": {
"status": "pending",
"display_name": "Finans Kulubu",
"nickname": "finanskulubu",
"updated": false,
"message": "Application submitted. We will review and notify you."
}
}
Get Leader Application Status
POST /user?request_type=community_leader_application_get
Returns the caller’s latest leader application so a client can show its status and prefill the edit form. No body required.
Response:
{
"ok": true,
"data": {
"status": "pending",
"is_leader": false,
"display_name": "Finans Kulubu",
"reason": "...",
"experience": "...",
"applied_at": 1784990000,
"twitter": "",
"instagram": "",
"youtube": "",
"telegram": ""
}
}
status is one of none, pending, approved, rejected, or withdrawn.
An approved leader returns {"status": "approved", "is_leader": true}; a user
who never applied returns {"status": "none", "is_leader": false}.
Withdraw Leader Application
POST /user?request_type=community_leader_application_withdraw
Cancels a pending leader application. The record is marked withdrawn
(history is kept) and you may apply again later. No body required.
Response:
{"ok": true, "data": {"status": "withdrawn"}}
Errors: 404 if there is no application, 409 if the latest application is not pending (already approved/rejected/withdrawn).
Update Leader Profile
POST /user?request_type=community_leader_update
Only accessible by users with approved "leader" community role.
Field |
Type |
Required |
Description |
|---|---|---|---|
|
string |
Yes |
New Leader Display Name (3-30 Latin alphanumeric chars or spaces) |
|
string |
No |
Updated bio / description |
|
string |
No |
Optional new avatar image URL |
Response:
{
"ok": true,
"data": {
"display_name": "Finans Kulubu Pro",
"nickname": "finanskulubupro",
"community_bio": "Crypto & Forex specialist",
"avatar_url": "https://..."
}
}
Errors
All errors return:
{"error": "Descriptive error message"}
Authentication-related errors add an optional machine-readable code marker.
The rest of the body shape is unchanged:
{"error": "Invalid JWT token!", "code": "session_expired"}
Code |
Meaning |
|---|---|
400 |
Bad request / validation error |
401 |
Credential rejected, revoked, or session token expired |
402 |
Insufficient credits |
403 |
Forbidden / license expired |
404 |
Not found |
405 |
Invalid request_type |
409 |
Conflict (duplicate) |
429 |
Rate limited |
500 |
Server error |
502 |
Upstream exchange did not return a usable response |
503 |
A backing store the request needs is temporarily unavailable |
Error code markers
code is optional. When present it identifies the class of authentication
failure so clients can react without parsing the message text.
|
Meaning |
Client action |
|---|---|---|
|
The session token itself was missing, invalid, or expired. |
Clear the stored session and re-authenticate. |
|
The Developer API key/secret was rejected or has been revoked. |
Issue new API credentials. An authenticated app session is unaffected. |
|
The Developer API key store could not be reached, so no credential was evaluated. Always paired with HTTP 503. |
Retry with backoff. Do not rotate credentials and do not clear the session. |
api_key_store_unavailable is the answer for a missing dependency, not a
rejected credential: it is never a 401 and never a bare 500. It applies to
api_key_generate, api_key_revoke, X-API-Key /
X-API-Secret authentication on /user, and the same header pair on
/tradingdata. The response body stays generic — no table, resource, or
internal error detail is exposed.
session_expired is returned for a missing, malformed, or expired session
token, for a missing caller identity, and for every operation that rejects an
unauthenticated caller on /user and /tradingdata. The dedicated
token_expired response carries it as well:
{"error": "token_expired", "code": "session_expired", "message": "Your session has expired. Please log in again."}
Clients must clear the local session and re-authenticate only when a 401
carries "code": "session_expired". A 401 without a code field is a
business error, not a session problem, and must not sign the user out.
Account linking and login status codes
These conditions previously returned HTTP 401. They now return a condition-specific status, so a client no longer mistakes them for an expired session:
Condition |
Status |
|---|---|
Binance API key or secret is not exactly 64 characters, on the credential-linking path ( |
400 |
Binance API key or secret is not exactly 64 characters, on the login path ( |
401 |
Binance returned no account ID while linking ( |
502 |
Login with a Binance identity that is not registered ( |
400 |
Login where the supplied Google identity does not match the stored one ( |
409 |
None of these responses carry a code marker. In particular, the 401 on the
login path above is a business error, not a session_expired — clients must
follow the rule above and only sign out on "code": "session_expired".
Backtest estimate modes
POST /backtest?action=estimate accepts the common strategy, coin(s), and
timeframe fields. Without timestamps it estimates a multi-coin full_range
operation, including its success charge. When both start_ts and end_ts are
provided it estimates a single-coin run, clips the timestamps to available
data, and returns the range candle count and standard run cost. Supplying only
one timestamp, an inverted range, or multiple coins in range mode returns 400.
The response includes mode, either full_range or range.
Track Trend Break Signal
Track trend break signals to your personal watchlist. Tracked signals are preserved (do not expire via TTL) as long as at least one user is tracking them.
Note
The previously published track_signal, untrack_signal and tracked_signals
names have been removed and now return 405. Use the canonical names below.
Track a Trend Break Signal
GET /tradingdata?request_type=track_trend_break_signal
Adds the authenticated user to a signal’s tracking list and removes the signal’s TTL (preventing automatic expiration).
Field |
Type |
Required |
Description |
|---|---|---|---|
|
string |
Yes |
Coin symbol (e.g. |
|
string |
Yes |
Timeframe in minutes (e.g. |
|
string |
Yes |
Signal direction: |
|
string |
Yes |
Signal epoch timestamp in seconds |
Response:
{"tracked": true}
Errors:
400: Missing required fields
401: Authentication required (
"code": "session_expired")404: Signal not found (expired or never existed)
Untrack a Trend Break Signal
GET /tradingdata?request_type=untrack_trend_break_signal
Removes the authenticated user from a signal’s tracking list. If no users remain tracking the signal, the TTL is re-applied and the signal will eventually expire.
Field |
Type |
Required |
Description |
|---|---|---|---|
|
string |
Yes |
Coin symbol |
|
string |
Yes |
Timeframe in minutes |
|
string |
Yes |
Signal direction |
|
string |
Yes |
Signal epoch timestamp |
Response:
{"tracked": false}
Errors:
400: Missing required fields
401: Authentication required (
"code": "session_expired")
Get Tracked Trend Break Signals
GET /tradingdata?request_type=tracked_trend_break_signals
Returns all trend break signals the authenticated user is currently tracking.
Field |
Type |
Required |
Description |
|---|---|---|---|
|
integer |
No |
Max items per page (default 20, max 50) |
|
string |
No |
JSON-encoded pagination cursor from previous response |
Response:
{
"items": [
{
"coin_name": "BTC",
"graph_type": "240",
"way": "BUY",
"timestamp": "1720000000",
"is_return_to_trend": false,
"klines_data": [...],
"trend_data": {"high_trend": [...], "low_trend": [...]}
}
],
"last_key": null
}
Response items use the same schema as trend_signals — full signal data including klines and trend lines. Internal fields (gsi_pk, ttl, telegram_link, tracked_users) are stripped from the response.
Errors:
401: Authentication required (
"code": "session_expired")
Comment on Post
POST /user?request_type=community_post_commentField
Type
Required
Description
post_idstring
Yes
commentstring
Yes
Max 300 characters
Response: