Skip to content

Ошибки, retries и лимиты

Формат ошибок /api/v1/*

Все Public API v1 endpoints возвращают единый JSON envelope:

json
{
  "error": {
    "code": "invalid_signal",
    "message": "Signal validation failed.",
    "traceId": "0HN8A1BC23DEF:00000001",
    "details": [
      {
        "field": "ttlMs",
        "code": "signal_expired"
      }
    ]
  },
  "requestId": "optional_request_correlation"
}
ПолеНазначение
error.codeстабильный machine-readable code
error.messageтекст для диагностики; не используйте для ветвления программы
error.traceIdcorrelation server log; указывайте при обращении в поддержку
error.detailsмассив ошибок отдельных полей, обычно только для invalid_signal
requestIdприсутствует, когда backend уже успел создать или найти signal request

Всегда принимайте неизвестный error.code как допустимое расширение API и сохраняйте полный response для диагностики.

Чтение ошибки в трёх языках

bash
curl --silent --show-error \
  --dump-header /tmp/tmpro-headers.txt \
  --output /tmp/tmpro-body.json \
  'https://tradingmonitor.pro/api/v1/signals/REPLACE_WITH_REQUEST_ID' \
  --header 'Authorization: Bearer tmpro_api_REPLACE_ME'

cat /tmp/tmpro-headers.txt
cat /tmp/tmpro-body.json
csharp
using System.Text.Json;

using var response = await http.SendAsync(request);
var text = await response.Content.ReadAsStringAsync();

if (!response.IsSuccessStatusCode)
{
    using var json = JsonDocument.Parse(text);
    var root = json.RootElement;
    var error = root.GetProperty("error");
    var code = error.GetProperty("code").GetString();
    var traceId = error.GetProperty("traceId").GetString();
    var requestId = root.TryGetProperty("requestId", out var id)
        ? id.GetString()
        : null;
    var retryAfter = response.Headers.RetryAfter?.Delta;

    Console.Error.WriteLine(
        $"HTTP {(int)response.StatusCode}; code={code}; " +
        $"requestId={requestId}; traceId={traceId}; retryAfter={retryAfter}");
}
python
response = requests.get(url, headers=headers, timeout=30)

if not response.ok:
    body = response.json()
    error = body.get("error", {})
    print(
        "HTTP", response.status_code,
        "code=", error.get("code"),
        "requestId=", body.get("requestId"),
        "traceId=", error.get("traceId"),
        "Retry-After=", response.headers.get("Retry-After"),
    )

Общие коды Public API v1

HTTPCodeЗначение
401invalid_api_keybearer key отсутствует, неверен, истёк, отозван или пользователь удалён
403insufficient_scopekey действителен, но требуемый scope отсутствует
429api_rate_limitedпревышен rate limit API key или лимит неаутентифицированных попыток
500internal_errorнеобработанная server error до начала response
503public_api_disabledоператор временно отключил Public API v1

Authorization должен иметь вид:

http
Authorization: Bearer tmpro_api_<secret>

Public API key нельзя заменить пользовательским JWT или sessionToken.

Коды signal endpoints

POST /api/v1/signals

HTTPCodeRetry
400invalid_idempotency_keyисправить header; автоматический retry того же неверного запроса бесполезен
400malformed_jsonисправить JSON
403account_frozenнет, требуется изменение состояния аккаунта
409request_in_progressда, повторить тот же body и key после Retry-After
409idempotency_conflictнет; key уже связан с другим body
409idempotency_outcome_unknownне отправлять новую торговую операцию; проверить terminal/broker
409duplicate_signal_idнет; signalId уже использован
413request_body_too_largeуменьшить body
415unsupported_media_typeустановить Content-Type: application/json
422invalid_signalисправить поля по error.details
429signal_rate_limit_exceededда, после Retry-After; account quota
429daily_signal_limit_exceededтолько после сброса/изменения account quota
500signal_processing_failedне использовать новый key; outcome мог произойти

GET /api/v1/signals/{requestId}

HTTPCodeЗначение
404request_not_foundrequest отсутствует или принадлежит другому пользователю

Validation details для invalid_signal

fieldcodeПричина
$object_requiredJSON root не является object
vunsupported_versionотсутствует integer 1
signalIdinvalid_lengthотсутствует, пустой или длиннее 128 символов
typeinvalid_lengthотсутствует, пустой или длиннее 64 символов
cmdinvalid_lengthотсутствует, пустой или длиннее 64 символов
tsinvalid_unix_millisecondsзначение отсутствует, не integer или вне диапазона Unix milliseconds
tssignal_from_futuretimestamp дальше допустимого clock skew
ttlMsinvalid_ttlне integer либо вне 1..300000
ttlMssignal_expiredts + ttlMs уже в прошлом
ttlMstimestamp_overflowсумма timestamp и TTL вне допустимого диапазона
platformrequiredполе отсутствует или пустое
platformunsupported_platformодин из comma-separated tokens неизвестен
payloadobject_requiredpayload отсутствует или не object
instanceIdinvalid_lengthесли поле передано, оно пустое, не string или длиннее 128 символов

Ошибки terminal-specific payload, например неизвестный executionType или отсутствие limitPrice, возникают уже после принятия envelope. Они отражаются в executions[].status, reasonCode и terminal events, а не как HTTP 422.

Коды exact cancel

HTTPCodeЗначение
400invalid_idempotency_keyнет корректного единственного header
404order_not_foundorderRef не найден, чужой или имеет недопустимый формат
409order_not_cancellableордер не находится в отменяемом tracked state
409terminal_unavailableexact target сейчас недоступен или не поддерживает cancel capability
409cancel_already_in_progressпредыдущая отмена ещё не финальна
409idempotency_conflictkey уже использован для другого order

Финальный broker/terminal outcome является не HTTP-ошибкой, а executions[].cancel.status. См. Точная отмена ордера.

Коды Terminal Events API

HTTPCodeЗначение
400invalid_terminal_sourcequery не задаёт точные platform, instanceId, accountName
400invalid_event_cursorLast-Event-ID повторён или длиннее 80 символов
404terminal_source_not_foundsource отсутствует среди активных sources пользователя
429too_many_event_subscriptionsуже открыты 3 SSE subscriptions пользователя

replay_gap передаётся как SSE event после успешного HTTP 200, а не как HTTP error:

text
event: replay_gap
data: {"reason":"cursor_not_available"}

Ошибки login и API-key management

Endpoints /api/auth/login и /api/api-keys используют более простой legacy формат:

json
{
  "code": "invalid_credentials",
  "message": "Invalid email or password."
}

Он не содержит error, traceId или details.

Login

HTTPCodeЗначение
400ASP.NET validation responseemail/password не прошли model validation
401invalid_credentialsневерный email или password
403email_not_confirmedemail не подтверждён
429rate_limit_exceededболее 10 попыток в минуту для client partition

API-key management

EndpointHTTPCodeЗначение
все401может отсутствоватьJWT отсутствует, истёк или недействителен
все403business_plan_requiredтекущий plan не Business
create400invalid_nameимя не содержит 1–80 символов после trim
create400invalid_scopesscope отсутствует, неизвестен или повторён
create400invalid_expirationexpiration не в будущем или дальше 365 дней
create400active_key_limit_reachedуже существует 5 активных keys
create503public_api_disabledсоздание keys временно отключено
delete404может отсутствоватьkey id не существует или принадлежит другому пользователю

Правила retry

Безопасный алгоритм для mutating request

  1. До запроса создайте Idempotency-Key и сохраните его вместе с byte-identical signal body или точным orderRef.
  2. При network timeout повторите тот же запрос с тем же key.
  3. При request_in_progress дождитесь Retry-After и повторите тот же запрос.
  4. При 429 дождитесь Retry-After, если он есть, затем примените exponential backoff с jitter.
  5. При 5xx повторяйте только идемпотентно — с тем же key и теми же данными.
  6. При idempotency_outcome_unknown или signal_processing_failed не создавайте новый key для той же торговой операции.

Для read-only GET безопасен обычный exponential backoff. Для SSE используйте Last-Event-ID и интервалы 1, 2, 5, 10 секунд.

Пример backoff

text
attempt 1: 1 s + jitter
attempt 2: 2 s + jitter
attempt 3: 5 s + jitter
attempt 4+: 10 s + jitter

Ограничивайте общее время retry и предоставляйте оператору traceId, requestId и Idempotency-Key без раскрытия API secret.

Текущие технические лимиты

Значения ниже соответствуют текущей backend-конфигурации и могут быть изменены оператором.

ЛимитЗначение
Signal request body65 536 bytes
JSON depth16
Максимальный ttlMs300 000 ms
Допустимое будущее смещение ts60 s
Активные API keys5 на пользователя
Авторизованные Public API requests1200/min на API key
Неавторизованные попытки60/min на client partition
Login10/min на client partition
Одновременные SSE streams3 на пользователя
Terminal active heartbeat window120 s
Signal execution confirmation timeout60 s
Cancel confirmation timeout30 s
Signal idempotency retention24 h
Stale processing timeout5 min
Terminal event replay1000 events или 10 min на пользователя
Pending live events256 на SSE subscriber
Completed execution retention30 days

Account signal quotas зависят от тарифного плана и применяются совместно к сигналам из Public API и других поддерживаемых каналов. SSE limit не влияет на количество подключённых устройств тарифного плана.

Безопасность логов

Не записывайте в application logs:

  • полный tmpro_api_... key;
  • пользовательский JWT;
  • password;
  • SignalR session token.

Можно записывать displayPrefix, requestId, executionId, orderRef, cancelRequestId, eventId, HTTP status, error.code и traceId.

TradingMonitor.Pro documentation.