Chyby a opakování požadavků (CZ)
Obálky chyb, chybové kódy pro jednotlivé rozhraní, strategie opakování, idempotence a observabilita pro ESM partnery.
Obálka chyby
Každá chybová REST odpověď z Voke má stejný JSON tvar:
{
"statusCode": 400,
"message": "Human-readable description",
"error": "Bad Request",
"code": "VALIDATION_ERROR",
"details": ["field: message"]
}Pole code je stabilní kontrakt — jde o řetězec z enumu ApiErrorCode, který se mezi minor verzemi nezmění. message a details jsou informativní a mohou se změnit. Logiku zpracování chyb stavte na code, nikoli na message.
Úplný seznam všech kódů a jejich mapování na HTTP stavy najdete v Reference / Error Codes.
REST chyby, na které partneři narazí
Toto jsou hodnoty ApiErrorCode, na které partner pravděpodobně narazí při volání REST API Voke (správa API klíčů, trading konfigurace, endpointy org-contextu).
| Code | HTTP | Kdy nastává |
|---|---|---|
UNAUTHORIZED | 401 | Chybějící/expirovaný API klíč, nebo (na partnerských endpointech) chybějící hlavička X-API-Key. |
FORBIDDEN | 403 | Platné přihlašovací údaje, ale nedostatečné scopes pro daný zdroj. |
BAD_REQUEST | 400 | Obecně nesprávně formovaný požadavek — neshoda content-type, rozbité JSON apod. |
VALIDATION_ERROR | 400 | Tělo požadavku nebo query parametry neprošly validací schématu. details[] vypíše chybná pole. |
INVALID_UUID | 400 | Path nebo query parametr očekával UUID, ale obdržel něco jiného. |
EXTERNAL_PLANT_ID_INVALID_FORMAT | 400 | externalPlantId neodpovídá formátu partnerského identifikátoru. |
PAYLOAD_TOO_LARGE | 413 | Tělo požadavku překračuje limit dané route (výchozí 5 MB na /api/v1/*). |
NOT_FOUND | 404 | Obecná 404 pro non-VCP routes. |
SITE_NOT_FOUND | 404 | siteId buď neexistuje, nebo není viditelné pro org vašeho API klíče. Cross-org přístup vrací stejnou 404 — nikdy ne 403 — aby neunikla existence sites z jiných org. |
PLANT_NOT_FOUND | 404 | Totéž jako SITE_NOT_FOUND, ale pro starší rodinu routes /plants/* (ponechána vedle /vcp/sites/*, dokud konzumenti migrují). |
CONFLICT | 409 | Porušení unique constraintu (např. duplicitní název API klíče). |
API_KEY_NOT_FOUND | 404 | Revoke/lookup neexistujícího API klíče. |
API_KEY_SCOPES_EMPTY | 400 | Mint požadavek neobsahuje žádné scopes. |
API_KEY_SCOPE_INVALID | 400 | Jeden nebo více požadovaných scopes není rozpoznáno. |
API_KEY_CIDR_NOT_IMPLEMENTED | 400 | allowedIps v mint požadavku používá tvar CIDR, který server zatím nepodporuje. |
API_KEY_VCP_SCOPES_NOT_SUPPORTED | 400 | Nasazení zatím neprovisionuje per-key vhosty, takže scopes vcp:* nelze novým klíčům udělit. |
TRADING_NOT_ENABLED | 400 | Trading nebyl pro organizaci povolen. |
TRADING_CONFIG_INCOMPLETE | 400 | Trading je povolen, ale chybí povinná konfigurační pole (ESM URL, přihlašovací údaje). |
ESM_NOT_CONFIGURED | 400 | Org nemá záznam TradingPartnerConfig. |
PLANT_NOT_LINKED_TO_ESM | 400 | Plant nemá nastaveno external_plant_id. |
TOO_MANY_REQUESTS | 429 | Překročen rate limit na IP. Může být přítomna hlavička Retry-After v sekundách (best-effort: závisí na throttler middleware na dané route). Počkejte alespoň tuto dobu a poté zopakujte jednou. |
SERVICE_UNAVAILABLE | 503 | Downstream závislost (broker, databáze) je nedostupná. Opakujte s exponenciálním backoffem. |
INTERNAL_SERVER_ERROR | 500 | Neočekávaná chyba serveru. Lze bezpečně opakovat s exponenciálním backoffem. |
Kódy odmítnutí VCP příkazů
Když Voke odmítne příkaz, AMQP ack zpráva (event.command.ack) má status: "REJECTED" a rejectionCode z enumu RejectionCode.
rejectionCode | Význam |
|---|---|
CONSTRAINT_VIOLATION | Hodnota příkazu porušuje omezení site (např. setpoint mimo povolený rozsah) |
INVALID_COMMAND | Typ příkazu není pro tuto site/device podporován |
INVALID_PAYLOAD | Payload příkazu je strukturálně nesprávný |
DEVICE_OFFLINE | Cílové zařízení není dostupné |
DEVICE_FAULT | Cílové zařízení je ve stavu poruchy a nemůže přijímat příkazy |
UNSUPPORTED_FOR_TOPOLOGY | Příkaz je obecně platný, ale není podporován pro fyzickou topologii tohoto plantu |
Nikdy neopakujte odmítnutý příkaz se stejným messageId a stejným payloadem. Ack REJECTED je sémantické odmítnutí, nikoli přechodné selhání. Změňte payload (např. dostaňte setpoint do rozsahu) nebo eskalujte na operations, než budete opakovat.
Selhání AMQP autentizace
Selhání AMQP autentizace a autorizace nejsou HTTP chyby — nastávají na úrovni RabbitMQ protokolu při navazování spojení nebo otevírání kanálu. Voke používá rabbitmq_auth_backend_http k validaci přihlašovacích údajů na straně serveru.
| Selhání | Viditelné jako | Příčina |
|---|---|---|
| Vyjednáno TLS 1.2 | Selhání TLS handshake (protocol_version / SSLHandshakeException) ještě před AMQP auth | Broker je pouze TLS 1.3. Klient, který vyjedná TLS 1.2, je odmítnut — např. Spring ssl.enabled: true bez ssl.algorithm: TLSv1.3, nebo bezparametrové useSslProtocol() v RabbitMQ Java klientu. Explicitně připněte TLS 1.3. |
| Špatné username (slug) nebo API klíč | AMQP 403 Connection refused | Přihlašovací údaje nenalezeny nebo nejsou spojeny se scope vcp:connect |
| API klíč zneplatněn (revoked) | AMQP 403 Connection refused | Klíč byl na straně Voke smazán; znovu jej vytvořte a aktualizujte konfiguraci konzumenta |
| Špatný vhost | AMQP 403 Access refused | Použijte per-key vhost partner-{keyId} z vašeho connection bundle; nikdy se nevracejte k / (výchozí vhost). Každý klíč vcp:* si automaticky provisionuje vlastní vhost, již vložený v connection URI bundle. |
| Neshoda názvu fronty | AMQP 403 Access refused | Konzument deklaroval frontu mimo namespace vcp.{slug}.* |
| Chybějící publish scope | AMQP 403 Access refused | Publish route vyžaduje odpovídající scope vcp:write:* |
| Chybějící/neplatné HMAC | Zpráva je přijata brokerem, poté zahozena Voke | command.device, command.mode a schedule.* vyžadují platný podpis obálky |
Tato selhání jsou na straně Voke logována v AmqpAuthController. Partneři vidí pouze odmítnutí na úrovni protokolu. Pokud spojení, které dříve fungovalo, najednou začne selhávat, nejpravděpodobnější příčinou je rotace nebo zneplatnění API klíče — vytvořte nový klíč se scope vcp:connect a aktualizujte svého konzumenta.
Doporučení k opakování
REST
| Třída odpovědi | Strategie |
|---|---|
| 4xx (kromě 429) | Neopakujte. Opravte payload požadavku, přihlašovací údaje nebo org context. |
| 429 Too Many Requests | Počkejte na hodnotu Retry-After (sekundy) z hlavičky odpovědi, poté zopakujte jednou. |
| 5xx | Opakujte s exponenciálním backoffem: 1 s, 2 s, 4 s, 8 s, 16 s. Maximálně 5 pokusů. Při selhání všech to vzdejte a vyvolejte alert. |
VCP (AMQP zprávy)
| Scénář | Strategie |
|---|---|
Přijat ack status: "ACCEPTED" nebo "QUEUED" | Než vyhodnotíte výsledek příkazu, počkejte na event.execution. |
Přijat ack status: "PARTIAL" | Prozkoumejte results[]; část batch položek byla přijata a část odmítnuta. |
Přijat ack status: "REJECTED" | Neopakujte. Změňte payload nebo eskalujte. |
| Žádný ack v rámci vašeho timeoutu | Zkontrolujte, zda messageId již nebylo zpracováno (nahlédněte do vlastního logu). Pokud ne, můžete zopakovat s novým messageId poté, co potvrdíte, že site je dostupná. |
| Reconnect konzumenta | RabbitMQ znovu doručí všechny nepotvrzené (unacknowledged) zprávy. Očekávejte duplikáty. Deduplikujte podle messageId na straně konzumenta. |
event.execution opožděn nebo chybí | Jde o timing problém fan-outu na straně Voke — příkaz spekulativně neposílejte znovu. Před eskalací počkejte alespoň 30 s na opožděné doručení event.execution. |
Výpadky AMQP spojení
Připojte se znovu okamžitě (bez čekání při prvním pokusu), poté při dalších selháních aplikujte backoff. Fronty jsou durable a zprávy jsou persistent — během vašeho odpojení se nic neztratí. Nepotvrzené (unacked) zprávy se po opětovném připojení automaticky znovu doručí.
Idempotence
- Vždy nastavte unikátní
messageIdv každé VCP obálce, kterou odesíláte. Použijte UUID v4 nebo identifikátor odolný proti kolizím.messageIdje vaše hlavní vodítko pro korelaci acků a statusů. - Voke deduplikuje příchozí příkazy podle
messageIdpo dobu 10 minut přes Redis (vcp:seen:{orgSlug}:{messageId}, atomickéSET NX EX 600). První doručení proběhne; duplikáty uvnitř okna jsou potvrzeny na brokeru a tiše zahozeny — na replay se partnerovi nepublikuje zpět žádný ACK, takže na něj nečekejte. Pokud downstream zpracování vyhodí výjimku, Voke uvolní claim, takže legitimní opakování se stejnýmmessageIdproběhne. Mimo 10minutové okno bude stejnémessageIdzpracováno znovu — navrhujte příkazy tak, aby byly sémanticky idempotentní (scheduleId,correlationId, stabilní reference příkazů), místo spoléhání pouze na replay guard. Úplný kontrakt replay guardu viz VCP message integrity. - Partneři musí deduplikovat příchozí události podle
messageIdlokálně. Po reconnectu konzumenta může RabbitMQ znovu doručit poslední nepotvrzený batch. Pro většinu případů postačí jednoduchá in-memory LRU množina posledních N hodnotmessageId.
Observabilita
Partneři typicky logují každý příchozí ack a status event klíčovaný podle correlationId, aby šel kompletní round-trip příkazu rekonstruovat jen z partnerských logů:
[correlationId: req-abc123] SEND command.site-setpoint messageId: msg-001
[correlationId: req-abc123] ACK QUEUED messageId: msg-001-ack
[correlationId: req-abc123] STATUS EXECUTING messageId: msg-001-status-1
[correlationId: req-abc123] STATUS COMPLETED messageId: msg-001-status-2Na straně Voke VcpCommandLog uchovává stejnou timeline — každý příkaz, ack a status event je řádek klíčovaný podle messageId a correlationId. Při zakládání incidentu uveďte messageId a Voke ops během sekund najde odpovídající řádky VcpCommandLog.
Související odkazy
- Reference / Error Codes — úplná tabulka
ApiErrorCodes HTTP stavem a popisy - Commands — typy příkazů, routing keys, ack flow
- VCP message integrity — struktura obálky a podpis