Version: 1.0.0
Category: Error
Status: Stable
The Error Handling Protocol defines how AI agents should handle errors when interacting with Structs APIs. This includes error response formats, retry strategies, error recovery patterns, and best practices.
Key Principles:
Format:
{
"code": 2,
"message": "codespace structs code 1900: object not found",
"details": []
}
Fields:
code (number): Error code (2 = not found)message (string): Human-readable error message with codespace and error codedetails (array): Additional error details (may be empty)Example:
{
"request": {
"method": "GET",
"url": "/structs/player/1-999",
"headers": {
"Accept": "application/json"
}
},
"response": {
"status": 400,
"body": {
"code": 2,
"message": "codespace structs code 1900: object not found",
"details": []
}
}
}
Every webapp (structs-webapp) JSON response — success or failure — uses the same ApiResponseContentDto envelope. On failure the HTTP status is 400 (bad request / validation), 401 (unauthenticated or signature failure), 403, 404, or 409, success is false, and errors is a keyed object (NOT an array).
Format:
{
"success": false,
"errors": {
"signature_validation_failed": "Invalid signature"
},
"data": null
}
Fields:
success (boolean): false for errorserrors (object): Map of error_key → human-readable message. Empty {} on success. Never a string array.data (object |
array | null): null on error |
Example (validation failure on a catalog read with a missing required param):
{
"request": {
"method": "GET",
"url": "/api/stat/power/object/1-11/range/page/1",
"headers": {
"Accept": "application/json"
}
},
"response": {
"status": 400,
"body": {
"success": false,
"errors": {
"start_time_end_time_required": "start_time and end_time query params are required (unix seconds)"
},
"data": null
}
}
}
Parsing rule: always check
successfirst, then readerrors(keyed object) or unwrapdata. Never assume a top-levelerror/code/detailsbody for webapp responses — that shape is only used by the consensus network API above.
Standard HTTP Status Codes:
200 - Success400 - Bad Request (invalid request format)404 - Not Found (resource doesn’t exist)429 - Too Many Requests (rate limit exceeded)500 - Internal Server Error (server error)503 - Service Unavailable (service temporarily unavailable)All error codes are defined in schemas/errors.md. Categories include:
code: 0): Operation successfulcodes: 1-9): Game-specific errorscodes: 400, 404, 500): HTTP status codestimeout, network): Network-related errorsRetryable Errors (can be retried):
GENERAL_ERROR (code: 1)INVALID_SIGNATURE (code: 3)INSUFFICIENT_GAS (code: 4)INTERNAL_SERVER_ERROR (code: 500)REQUEST_TIMEOUT (timeout)NETWORK_ERROR (network)Non-Retryable Errors (should not be retried):
INSUFFICIENT_FUNDS (code: 2)INVALID_MESSAGE (code: 5)PLAYER_HALTED (code: 6)INSUFFICIENT_CHARGE (code: 7)INVALID_LOCATION (code: 8)INVALID_TARGET (code: 9)ENTITY_NOT_FOUND (code: 404)BAD_REQUEST (code: 400)Recommended Strategy for retryable errors:
{
"retryStrategy": {
"type": "exponential",
"maxRetries": 3,
"initialDelay": 1000,
"maxDelay": 10000,
"backoffMultiplier": 2,
"retryableErrors": ["1", "3", "4", "500", "timeout", "network"]
}
}
Implementation Example:
{
"retryLogic": {
"attempt": 1,
"delay": 1000,
"maxRetries": 3,
"backoff": {
"attempt1": 1000,
"attempt2": 2000,
"attempt3": 4000
}
}
}
Alternative Strategy for rate limiting:
{
"retryStrategy": {
"type": "fixed",
"maxRetries": 3,
"delay": 5000,
"retryableErrors": ["429"]
}
}
When receiving 429 Too Many Requests:
{
"error": {
"code": 429,
"name": "RATE_LIMIT_EXCEEDED",
"response": {
"retry_after": 60
},
"handling": {
"action": "wait",
"waitTime": "response.retry_after",
"retry": true
}
}
}
Strategy:
Retry-After header or retry_after in responseError: ENTITY_NOT_FOUND (404) or codespace structs code 1900
Recovery:
{
"error": "ENTITY_NOT_FOUND",
"recovery": {
"step1": "Verify entity ID format",
"step2": "Check if entity exists (list endpoint)",
"step3": "If doesn't exist, create or use alternative",
"step4": "If format invalid, correct and retry"
}
}
Example:
{
"scenario": "Get player 1-999 (doesn't exist)",
"error": {
"code": 2,
"message": "codespace structs code 1900: object not found"
},
"recovery": {
"action": "List all players to find valid IDs",
"fallback": "Use default player or create new"
}
}
Error: INSUFFICIENT_FUNDS (code: 2) or INSUFFICIENT_CHARGE (code: 7)
Recovery:
{
"error": "INSUFFICIENT_FUNDS",
"recovery": {
"step1": "Check current resources (query player/struct)",
"step2": "Wait for resources to accumulate",
"step3": "Monitor resource generation",
"step4": "Retry when resources available"
}
}
Example:
{
"scenario": "Action requires charge but the player's shared charge bar is too low",
"error": {
"code": 7,
"name": "INSUFFICIENT_CHARGE",
"description": "Player has insufficient charge (charge is per-player, not per-struct)"
},
"recovery": {
"action": "Query player charge level (CurrentBlockHeight - player.lastActionBlock)",
"wait": "Monitor charge until sufficient",
"retry": "Retry the action when charge >= required"
}
}
Error: PLAYER_HALTED (code: 6)
Recovery:
{
"error": "PLAYER_HALTED",
"recovery": {
"step1": "Check player status (query player)",
"step2": "Wait for player to come online",
"step3": "Monitor player status",
"step4": "Retry when player online"
}
}
Example:
{
"scenario": "Player is offline/halted",
"error": {
"code": 6,
"name": "PLAYER_HALTED",
"description": "Player is halted (offline)"
},
"recovery": {
"action": "Query player status periodically",
"wait": "Wait for player to come online",
"retry": "Retry action when player.halted = false"
}
}
Error: NETWORK_ERROR or REQUEST_TIMEOUT
Recovery:
{
"error": "NETWORK_ERROR",
"recovery": {
"step1": "Retry with exponential backoff",
"step2": "Check network connectivity",
"step3": "Verify endpoint availability",
"step4": "Use alternative endpoint if available"
}
}
Example:
{
"scenario": "Network timeout during request",
"error": {
"code": "timeout",
"name": "REQUEST_TIMEOUT"
},
"recovery": {
"retry": {
"attempt1": "Wait 1 second, retry",
"attempt2": "Wait 2 seconds, retry",
"attempt3": "Wait 4 seconds, retry"
},
"fallback": "Log error and abort if all retries fail"
}
}
Pattern:
{
"responseHandling": {
"step1": "Check HTTP status code",
"step2": "Check response body for error fields",
"step3": "Categorize error (retryable vs non-retryable)",
"step4": "Apply appropriate handling strategy"
}
}
Pattern:
{
"errorLogging": {
"include": [
"error code",
"error message",
"request details (method, URL, params)",
"response details",
"timestamp",
"retry attempt (if applicable)"
]
}
}
Pattern for repeated failures:
{
"circuitBreaker": {
"failureThreshold": 5,
"timeout": 60000,
"halfOpenRetries": 1,
"states": ["closed", "open", "half-open"]
}
}
Strategy:
Pattern:
{
"fallback": {
"nonRetryableError": "Use cached data or default values",
"retryableError": "Retry with backoff",
"criticalError": "Abort operation and notify"
}
}
Pattern:
{
"retryValidation": {
"step1": "Verify error is retryable",
"step2": "Check retry count < max retries",
"step3": "Validate request parameters unchanged",
"step4": "Apply backoff delay",
"step5": "Retry request"
}
}
Complete error code catalog: See schemas/errors.md
Quick Reference:
0 - SUCCESS1 - GENERAL_ERROR (retryable)2 - INSUFFICIENT_FUNDS (not retryable)3 - INVALID_SIGNATURE (retryable)4 - INSUFFICIENT_GAS (retryable)5 - INVALID_MESSAGE (not retryable)6 - PLAYER_HALTED (not retryable)7 - INSUFFICIENT_CHARGE (not retryable)8 - INVALID_LOCATION (not retryable)9 - INVALID_TARGET (not retryable)400 - BAD_REQUEST (not retryable)404 - ENTITY_NOT_FOUND (not retryable)429 - RATE_LIMIT_EXCEEDED (retryable with delay)500 - INTERNAL_SERVER_ERROR (retryable)timeout - REQUEST_TIMEOUT (retryable)network - NETWORK_ERROR (retryable){
"scenario": "Get player that doesn't exist",
"request": {
"method": "GET",
"url": "/structs/player/1-999"
},
"response": {
"status": 400,
"body": {
"code": 2,
"message": "codespace structs code 1900: object not found"
}
},
"handling": {
"errorCode": 2,
"category": "notRetryable",
"action": "Verify player ID exists",
"recovery": {
"step1": "List all players to find valid IDs",
"step2": "Use valid player ID or create new player"
}
}
}
{
"scenario": "Rate limit exceeded",
"request": {
"method": "GET",
"url": "/structs/player"
},
"response": {
"status": 429,
"headers": {
"Retry-After": "60"
},
"body": {
"error": "Rate limit exceeded",
"code": 429,
"retry_after": 60
}
},
"handling": {
"errorCode": 429,
"category": "retryable",
"action": "Wait and retry",
"recovery": {
"waitTime": 60,
"retry": true,
"strategy": "fixed delay"
}
}
}
{
"scenario": "Network timeout",
"request": {
"method": "GET",
"url": "/structs/player/1-1"
},
"error": {
"code": "timeout",
"name": "REQUEST_TIMEOUT"
},
"handling": {
"errorCode": "timeout",
"category": "retryable",
"action": "Retry with exponential backoff",
"recovery": {
"attempt1": {
"delay": 1000,
"retry": true
},
"attempt2": {
"delay": 2000,
"retry": true
},
"attempt3": {
"delay": 4000,
"retry": true
},
"maxRetries": 3,
"fallback": "Log error and abort if all retries fail"
}
}
}
When querying, always check for errors:
{
"queryPattern": {
"request": "GET /structs/player/{id}",
"errorHandling": {
"checkStatus": true,
"checkBody": true,
"categorizeError": true,
"applyRecovery": true
}
}
}
When performing actions, handle errors appropriately:
{
"actionPattern": {
"request": "POST /cosmos/tx/v1beta1/txs",
"errorHandling": {
"transactionErrors": {
"INSUFFICIENT_FUNDS": "Wait for resources",
"INVALID_SIGNATURE": "Re-sign and retry",
"PLAYER_HALTED": "Wait for player online"
}
}
}
}
schemas/errors.mdschemas/responses.mdprotocols/query-protocol.mdprotocols/action-protocol.mdtechnical/api-reference.md#error-handlingLast Updated: January 2025