Appearance
Ошибки, 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.traceId | correlation 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.jsoncsharp
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
| HTTP | Code | Значение |
|---|---|---|
401 | invalid_api_key | bearer key отсутствует, неверен, истёк, отозван или пользователь удалён |
403 | insufficient_scope | key действителен, но требуемый scope отсутствует |
429 | api_rate_limited | превышен rate limit API key или лимит неаутентифицированных попыток |
500 | internal_error | необработанная server error до начала response |
503 | public_api_disabled | оператор временно отключил Public API v1 |
Authorization должен иметь вид:
http
Authorization: Bearer tmpro_api_<secret>Public API key нельзя заменить пользовательским JWT или sessionToken.
Коды signal endpoints
POST /api/v1/signals
| HTTP | Code | Retry |
|---|---|---|
400 | invalid_idempotency_key | исправить header; автоматический retry того же неверного запроса бесполезен |
400 | malformed_json | исправить JSON |
403 | account_frozen | нет, требуется изменение состояния аккаунта |
409 | request_in_progress | да, повторить тот же body и key после Retry-After |
409 | idempotency_conflict | нет; key уже связан с другим body |
409 | idempotency_outcome_unknown | не отправлять новую торговую операцию; проверить terminal/broker |
409 | duplicate_signal_id | нет; signalId уже использован |
413 | request_body_too_large | уменьшить body |
415 | unsupported_media_type | установить Content-Type: application/json |
422 | invalid_signal | исправить поля по error.details |
429 | signal_rate_limit_exceeded | да, после Retry-After; account quota |
429 | daily_signal_limit_exceeded | только после сброса/изменения account quota |
500 | signal_processing_failed | не использовать новый key; outcome мог произойти |
GET /api/v1/signals/{requestId}
| HTTP | Code | Значение |
|---|---|---|
404 | request_not_found | request отсутствует или принадлежит другому пользователю |
Validation details для invalid_signal
field | code | Причина |
|---|---|---|
$ | object_required | JSON root не является object |
v | unsupported_version | отсутствует integer 1 |
signalId | invalid_length | отсутствует, пустой или длиннее 128 символов |
type | invalid_length | отсутствует, пустой или длиннее 64 символов |
cmd | invalid_length | отсутствует, пустой или длиннее 64 символов |
ts | invalid_unix_milliseconds | значение отсутствует, не integer или вне диапазона Unix milliseconds |
ts | signal_from_future | timestamp дальше допустимого clock skew |
ttlMs | invalid_ttl | не integer либо вне 1..300000 |
ttlMs | signal_expired | ts + ttlMs уже в прошлом |
ttlMs | timestamp_overflow | сумма timestamp и TTL вне допустимого диапазона |
platform | required | поле отсутствует или пустое |
platform | unsupported_platform | один из comma-separated tokens неизвестен |
payload | object_required | payload отсутствует или не object |
instanceId | invalid_length | если поле передано, оно пустое, не string или длиннее 128 символов |
Ошибки terminal-specific payload, например неизвестный executionType или отсутствие limitPrice, возникают уже после принятия envelope. Они отражаются в executions[].status, reasonCode и terminal events, а не как HTTP 422.
Коды exact cancel
| HTTP | Code | Значение |
|---|---|---|
400 | invalid_idempotency_key | нет корректного единственного header |
404 | order_not_found | orderRef не найден, чужой или имеет недопустимый формат |
409 | order_not_cancellable | ордер не находится в отменяемом tracked state |
409 | terminal_unavailable | exact target сейчас недоступен или не поддерживает cancel capability |
409 | cancel_already_in_progress | предыдущая отмена ещё не финальна |
409 | idempotency_conflict | key уже использован для другого order |
Финальный broker/terminal outcome является не HTTP-ошибкой, а executions[].cancel.status. См. Точная отмена ордера.
Коды Terminal Events API
| HTTP | Code | Значение |
|---|---|---|
400 | invalid_terminal_source | query не задаёт точные platform, instanceId, accountName |
400 | invalid_event_cursor | Last-Event-ID повторён или длиннее 80 символов |
404 | terminal_source_not_found | source отсутствует среди активных sources пользователя |
429 | too_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
| HTTP | Code | Значение |
|---|---|---|
400 | ASP.NET validation response | email/password не прошли model validation |
401 | invalid_credentials | неверный email или password |
403 | email_not_confirmed | email не подтверждён |
429 | rate_limit_exceeded | более 10 попыток в минуту для client partition |
API-key management
| Endpoint | HTTP | Code | Значение |
|---|---|---|---|
| все | 401 | может отсутствовать | JWT отсутствует, истёк или недействителен |
| все | 403 | business_plan_required | текущий plan не Business |
| create | 400 | invalid_name | имя не содержит 1–80 символов после trim |
| create | 400 | invalid_scopes | scope отсутствует, неизвестен или повторён |
| create | 400 | invalid_expiration | expiration не в будущем или дальше 365 дней |
| create | 400 | active_key_limit_reached | уже существует 5 активных keys |
| create | 503 | public_api_disabled | создание keys временно отключено |
| delete | 404 | может отсутствовать | key id не существует или принадлежит другому пользователю |
Правила retry
Безопасный алгоритм для mutating request
- До запроса создайте
Idempotency-Keyи сохраните его вместе с byte-identical signal body или точнымorderRef. - При network timeout повторите тот же запрос с тем же key.
- При
request_in_progressдождитесьRetry-Afterи повторите тот же запрос. - При
429дождитесьRetry-After, если он есть, затем примените exponential backoff с jitter. - При
5xxповторяйте только идемпотентно — с тем же key и теми же данными. - При
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 body | 65 536 bytes |
| JSON depth | 16 |
Максимальный ttlMs | 300 000 ms |
Допустимое будущее смещение ts | 60 s |
| Активные API keys | 5 на пользователя |
| Авторизованные Public API requests | 1200/min на API key |
| Неавторизованные попытки | 60/min на client partition |
| Login | 10/min на client partition |
| Одновременные SSE streams | 3 на пользователя |
| Terminal active heartbeat window | 120 s |
| Signal execution confirmation timeout | 60 s |
| Cancel confirmation timeout | 30 s |
| Signal idempotency retention | 24 h |
| Stale processing timeout | 5 min |
| Terminal event replay | 1000 events или 10 min на пользователя |
| Pending live events | 256 на SSE subscriber |
| Completed execution retention | 30 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.