Error Reference
Complete error code reference for all JarvisClaw API services. Includes HTTP status codes, x402 payment errors, and service-specific error codes.
Base URL: https://api.jarvisclaw.ai/v1
HTTP Status Codes
| Code | Name | Description | Resolution |
|---|---|---|---|
| 400 | Bad Request | Malformed request body or missing required parameters. | Check request format and required fields. |
| 401 | Unauthorized | Invalid or missing API key. | Verify your API key is correct and active. |
| 402 | Payment Required | x402 payment needed or insufficient USDC balance. | Top up USDC wallet or check payment signature. |
| 403 | Forbidden | API key lacks permission for this resource. | Check key permissions in dashboard. |
| 404 | Not Found | Endpoint or resource does not exist. | Verify the URL path is correct. |
| 429 | Rate Limited | Too many requests in time window. | Back off and retry after the Retry-After header value. |
| 500 | Internal Server Error | Unexpected server failure. | Retry with exponential backoff; contact support if persistent. |
| 502 | Bad Gateway | Upstream provider unavailable. | Retry; the provider may be temporarily down. |
| 503 | Service Unavailable | Service overloaded or in maintenance. | Wait and retry after a few seconds. |
Error Body Shapes
Two shapes are in circulation, so handle both.
OpenAI-compatible relay endpoints (/v1/chat/completions, /v1/messages, /v1/images/*, /v1/audio/*, …):
{ "error": { "message": "...", "type": "...", "code": "..." } }AIP, wallet, and marketplace endpoints:
{ "error": "human-readable message" }Both SDKs normalise this — Python's APIError.message and Go's APIError.Message read whichever form arrived, with the raw body on .body / .Body.
Don't branch on error code strings
Only the relay path emits a code, and the values are whatever the upstream provider returned. The AIP and wallet surfaces return a bare message string with no code at all. Branch on HTTP status and error type; treat any code as diagnostic text.
x402 Payment Failures
A payment problem surfaces as 402 with the x402 challenge body, or as a 402/502 carrying the facilitator's rejection message. Common causes:
| Cause | Symptom | Resolution |
|---|---|---|
| Insufficient USDC | 402 on the paid retry | Top up your wallet (Base or Solana) |
| Signature mismatch | Facilitator verification fails | Confirm the private key controls the from address |
| Nonce reuse | Replay rejection | SDKs generate a fresh random nonce per attempt |
| Authorization expired | Rejected after maxTimeoutSeconds (300s) | Re-sign and retry; SDKs do this automatically |
| Amount mismatch | Rejected on verification | Sign exactly the amount from the 402; do not round |
| Unsupported network | No usable option in accepts | Use Base (eip155:8453) or Solana mainnet |
| Missing Solana ATA | Solana path skipped | Receive one USDC transfer to initialise the token account |
| Price unavailable | 503 price_unavailable | Upstream price probe failed; retry shortly |
Solana is only advertised when the gateway knows a fee payer — if it doesn't, accepts lists Base alone. See Agent Payments (x402).
Common Failure Modes
| Situation | Status | Notes |
|---|---|---|
| Unknown or retired model | 400 | Check GET /v1/models; smart-route aliases need the auto/ prefix |
| Context window exceeded | 400 | Trim history or move to a larger-context model |
| Content filter | 400 / job failed | Async jobs report status: "failed" with retryable: false |
| Upstream provider error | 502 | Retry, or pin a different model |
| Concurrency cap hit | 429 | Marketplace services cap concurrent requests |
| Async job still running | 200 with status: "in_progress" | Keep polling until completed or failed |
Code Examples
# Check HTTP status and error body
curl -s -w "\nHTTP_STATUS:%{http_code}" \
https://api.jarvisclaw.ai/v1/chat/completions \
-H "Authorization: Bearer $JARVISCLAW_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"Hello"}]}' \
| tee >(tail -1 | grep -o "HTTP_STATUS:[0-9]*") \
| head -n -1 | jq .
# On error the body will contain:
# { "error": { "code": "invalid_model", "message": "..." } }
# or for x402:
# { "error": { "code": "insufficient_balance", "message": "..." } }import time
from jarvisclaw import (
OpenAI,
APIError,
AuthenticationError,
InsufficientBalanceError,
JarvisClawError,
RateLimitError,
)
# OpenAI is the drop-in client; ChatClient / JarvisClaw work the same way.
client = OpenAI(api_key="sk-your-api-key")
def chat_with_retry(messages, model="openai/gpt-4o-mini", max_retries=3):
for attempt in range(max_retries):
try:
return client.chat.completions.create(
model=model,
messages=messages,
)
except AuthenticationError as e:
# 401 — bad API key, no point retrying
print(f"Authentication failed: {e.message}")
raise
except InsufficientBalanceError as e:
# 402 — out of USDC; top up and retry manually
print(f"Insufficient balance: {e.message}")
print(f"Error code: {e.body.get('error', {}).get('code')}")
raise
except RateLimitError as e:
# 429 — back off using Retry-After when available
wait = e.retry_after or (2 ** attempt)
print(f"Rate limited. Retrying in {wait}s …")
time.sleep(wait)
except APIError as e:
# 500 / 502 / 503 — transient server errors
if e.status_code in (500, 502, 503) and attempt < max_retries - 1:
wait = 2 ** attempt
print(f"Server error {e.status_code}. Retrying in {wait}s …")
time.sleep(wait)
else:
# 400 / 403 / 404 or exhausted retries
code = e.body.get("error", {}).get("code", "unknown")
print(f"API error [{e.status_code}] {code}: {e.message}")
raise
except JarvisClawError as e:
# x402 payment / SDK-level errors
print(f"SDK error: {e}")
raise
raise RuntimeError("Max retries exceeded")package main
import (
"context"
"errors"
"fmt"
"time"
jarvisclaw "github.com/api-jarvisclaw/go-sdk/v2"
)
func chatWithRetry(
ctx context.Context,
client *jarvisclaw.Client,
messages []jarvisclaw.Message,
maxRetries int,
) (*jarvisclaw.ChatResponse, error) {
for attempt := range maxRetries {
resp, err := client.ChatCompletion(ctx, "openai/gpt-4o-mini", messages)
if err == nil {
return resp, nil
}
var authErr *jarvisclaw.AuthenticationError
if errors.As(err, &authErr) {
// 401 — invalid API key, no retry
return nil, fmt.Errorf("authentication failed: %w", err)
}
var balanceErr *jarvisclaw.InsufficientBalanceError
if errors.As(err, &balanceErr) {
// 402 — top up USDC wallet
return nil, fmt.Errorf("insufficient balance: %w", err)
}
var rateErr *jarvisclaw.RateLimitError
if errors.As(err, &rateErr) {
// 429 — back off
wait := time.Duration(1<<attempt) * time.Second
fmt.Printf("Rate limited. Retrying in %s ...\n", wait)
time.Sleep(wait)
continue
}
var apiErr *jarvisclaw.APIError
if errors.As(err, &apiErr) {
switch apiErr.StatusCode {
case 500, 502, 503:
// Transient server error — exponential backoff
if attempt < maxRetries-1 {
wait := time.Duration(1<<attempt) * time.Second
fmt.Printf("Server error %d. Retrying in %s ...\n", apiErr.StatusCode, wait)
time.Sleep(wait)
continue
}
default:
// 400 / 403 / 404 / upstream_error — not retryable
code := apiErr.Body["error"]
return nil, fmt.Errorf("api error [%d] %v: %s", apiErr.StatusCode, code, apiErr.Message)
}
}
// PaymentError / other SDK errors
return nil, fmt.Errorf("sdk error: %w", err)
}
return nil, fmt.Errorf("max retries (%d) exceeded", maxRetries)
}Error types carry no Type field
Go's APIError exposes StatusCode, Message, and Body — switch on the concrete error type or the status code, not a string discriminator. AuthenticationError, RateLimitError, and InsufficientBalanceError embed APIError; PaymentError embeds JarvisClawError. The Python hierarchy mirrors this — see SDK → Error Handling.