# Retries and Error Handling

As a starting point, use these scripts to add production-grade reliability to your integration.&#x20;

* They show how to apply timeouts, bounded retries, and exponential backoff when requests fail due to transient errors or rate limits, while avoiding retries on non-recoverable failures such as invalid authentication or malformed requests.&#x20;
* With exponential backoff, each retry waits longer than the last, often with added jitter, which reduces contention and prevents synchronized retry storms.&#x20;
* Retries continue until the request succeeds or a maximum attempt limit is reached, allowing your system to recover gracefully without dropping work or violating rate limits.
* Refer to these resources for more guidance:
  * <https://tenacity.readthedocs.io/en/latest/>
  * <https://pypi.org/project/backoff/>

{% tabs %}
{% tab title="Python" %}
{% code expandable="true" %}

```py
import base64
import hashlib
import json
import os
import time

import requests
from nacl.signing import SigningKey

BASE_URL = 'https://trading.api.thegrid.ai'
MAX_RETRIES = 3
RETRY_DELAY_SEC = 1


class SignatureAuth:
    def __init__(self, private_key_b64, public_key_b64):
        self.private_key = SigningKey(base64.b64decode(private_key_b64)[:32])
        public_key_bytes = base64.b64decode(public_key_b64)
        hash_digest = hashlib.sha256(public_key_bytes).digest()
        self.fingerprint = base64.b64encode(hash_digest).decode().rstrip('=')

    def get_headers(self, method, path, body=''):
        timestamp = str(int(time.time()))
        message = f'{timestamp}{method.upper()}{path}{body}'
        signature = self.private_key.sign(message.encode()).signature
        return {
            'x-thegrid-signature': base64.b64encode(signature).decode(),
            'x-thegrid-timestamp': timestamp,
            'x-thegrid-fingerprint': self.fingerprint,
        }


def buy_with_retry(auth, market_id, quantity, max_price):
    order_data = {
        'market_id': market_id,
        'side': 'buy',
        'type': 'limit',
        'quantity': quantity,
        'price': str(max_price),
        'time_in_force': 'gtc',
        'client_order_id': f'buy-{int(time.time() * 1000)}',
    }

    path = '/api/v1/trading/orders'
    body = json.dumps(order_data)

    for attempt in range(1, MAX_RETRIES + 1):
        try:
            # Generate fresh signature for each attempt (timestamp changes)
            headers = auth.get_headers('POST', path, body)
            headers['Content-Type'] = 'application/json'

            response = requests.post(f'{BASE_URL}{path}', data=body, headers=headers)
            response.raise_for_status()
            return {'success': True, 'order_id': response.json()['data']['order_id']}

        except requests.exceptions.RequestException as e:
            status = getattr(e.response, 'status_code', None) if hasattr(e, 'response') else None
            message = str(e)

            if hasattr(e, 'response') and e.response is not None:
                try:
                    message = e.response.json().get('errors', {}).get('detail', message)
                except (ValueError, KeyError):
                    pass

            print(f'Attempt {attempt}/{MAX_RETRIES} failed: {message}')

            # Don't retry on client errors (4xx) except rate limiting (429)
            if status and 400 <= status < 500 and status != 429:
                return {'success': False, 'error': message, 'retryable': False}

            # Wait before retrying (exponential backoff)
            if attempt < MAX_RETRIES:
                delay = RETRY_DELAY_SEC * (2 ** (attempt - 1))
                print(f'Retrying in {delay}s...')
                time.sleep(delay)

    return {'success': False, 'error': 'Max retries exceeded', 'retryable': True}


auth = SignatureAuth(
    os.environ['GRID_TRADING_PRIVATE_KEY'],
    os.environ['GRID_TRADING_PUBLIC_KEY'],
)

result = buy_with_retry(
    auth,
    'market_788dcbd5-ac68-4c61-acf1-4443beaf2a1c',
    1,
    2.50
)

if result['success']:
    print(f"Order placed: {result['order_id']}")
else:
    print(f"Order failed: {result['error']}")
    exit(1)


```

{% endcode %}
{% endtab %}

{% tab title="JavaScript" %}

```javascript
// Buy with Retries and Error Handling (Trading API)

import axios from 'axios';
import nacl from 'tweetnacl';
import util from 'tweetnacl-util';

function decodePrivateKey(privateKeyBase64) {
  const raw = util.decodeBase64(privateKeyBase64);
  if (raw.length === nacl.sign.seedLength) {
    return nacl.sign.keyPair.fromSeed(raw).secretKey;
  }
  if (raw.length === nacl.sign.secretKeyLength) {
    return raw;
  }
  throw new Error('Invalid private key length; expected 32-byte seed or 64-byte secret key');
}

const BASE_URL = 'https://trading.api.thegrid.ai';
const MAX_RETRIES = 3;
const RETRY_DELAY_MS = 1000;

class SignatureAuth {
  constructor(privateKeyBase64, fingerprint) {
    this.privateKey = decodePrivateKey(privateKeyBase64);
    this.fingerprint = fingerprint;
  }

  getHeaders(method, path, body = '') {
    const timestamp = Math.floor(Date.now() / 1000).toString();
    const message = `${timestamp}${method.toUpperCase()}${path}${body}`;
    const messageBytes = util.decodeUTF8(message);
    const signatureBytes = nacl.sign.detached(messageBytes, this.privateKey);
    return {
      'x-thegrid-signature': util.encodeBase64(signatureBytes),
      'x-thegrid-timestamp': timestamp,
      'x-thegrid-fingerprint': this.fingerprint
    };
  }
}

async function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function buyWithRetry(auth, marketId, quantity, maxPrice) {
  const orderData = {
    market_id: marketId,
    side: 'buy',
    type: 'limit',
    quantity: quantity,
    price: maxPrice.toString(),
    time_in_force: 'gtc',
    client_order_id: `buy-${Date.now()}`
  };

  const path = '/api/v1/trading/orders';
  const body = JSON.stringify(orderData);

  for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
    try {
      // Generate fresh signature for each attempt (timestamp changes)
      const headers = {
        'Content-Type': 'application/json',
        ...auth.getHeaders('POST', path, body)
      };

      const response = await axios.post(`${BASE_URL}${path}`, body, { headers });
      return { success: true, orderId: response.data.data.order_id };

    } catch (error) {
      const status = error.response?.status;
      const message = error.response?.data?.errors?.detail || error.message;

      console.log(`Attempt ${attempt}/${MAX_RETRIES} failed: ${message}`);

      // Don't retry on client errors (4xx) except rate limiting (429)
      if (status && status >= 400 && status < 500 && status !== 429) {
        return { success: false, error: message, retryable: false };
      }

      // Wait before retrying (exponential backoff)
      if (attempt < MAX_RETRIES) {
        const delay = RETRY_DELAY_MS * Math.pow(2, attempt - 1);
        console.log(`Retrying in ${delay}ms...`);
        await sleep(delay);
      }
    }
  }

  return { success: false, error: 'Max retries exceeded', retryable: true };
}

// Main
const auth = new SignatureAuth(
  process.env.GRID_TRADING_PRIVATE_KEY,
  process.env.GRID_TRADING_FINGERPRINT
);

const result = await buyWithRetry(
  auth,
  'market_788dcbd5-ac68-4c61-acf1-4443beaf2a1c',
  1,
  2.50
);

if (result.success) {
  console.log(`Order placed: ${result.orderId}`);
} else {
  console.log(`Order failed: ${result.error}`);
  process.exit(1);
}

```

{% endtab %}

{% tab title="Go" %}

```go
package main

import (
	"bytes"
	"crypto/ed25519"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"
)

const (
	baseURL       = "https://trading.api.thegrid.ai"
	maxRetries    = 3
	retryDelaySec = 1
)

type SignatureAuth struct {
	privateKey  ed25519.PrivateKey
	fingerprint string
}

func NewSignatureAuth(privateKeyB64, publicKeyB64 string) *SignatureAuth {
	privateKeyBytes, _ := base64.StdEncoding.DecodeString(privateKeyB64)
	var privateKey ed25519.PrivateKey
	if len(privateKeyBytes) == ed25519.SeedSize {
		privateKey = ed25519.NewKeyFromSeed(privateKeyBytes)
	} else {
		privateKey = ed25519.PrivateKey(privateKeyBytes)
	}

	publicKeyBytes, _ := base64.StdEncoding.DecodeString(publicKeyB64)
	hash := sha256.Sum256(publicKeyBytes)
	fingerprint := strings.TrimRight(base64.StdEncoding.EncodeToString(hash[:]), "=")

	return &SignatureAuth{privateKey: privateKey, fingerprint: fingerprint}
}

func (sa *SignatureAuth) GetHeaders(method, path, body string) map[string]string {
	timestamp := strconv.FormatInt(time.Now().Unix(), 10)
	message := timestamp + method + path + body
	signature := ed25519.Sign(sa.privateKey, []byte(message))
	return map[string]string{
		"x-thegrid-signature":   base64.StdEncoding.EncodeToString(signature),
		"x-thegrid-timestamp":   timestamp,
		"x-thegrid-fingerprint": sa.fingerprint,
	}
}

type BuyResult struct {
	Success   bool
	OrderID   string
	Error     string
	Retryable bool
}

func buyWithRetry(auth *SignatureAuth, marketID string, quantity int, maxPrice float64) BuyResult {
	orderData := map[string]any{
		"market_id":       marketID,
		"side":            "buy",
		"type":            "limit",
		"quantity":        quantity,
		"price":           fmt.Sprintf("%.2f", maxPrice),
		"time_in_force":   "gtc",
		"client_order_id": fmt.Sprintf("buy-%d", time.Now().UnixMilli()),
	}

	orderJSON, _ := json.Marshal(orderData)
	path := "/api/v1/trading/orders"

	for attempt := 1; attempt <= maxRetries; attempt++ {
		// Generate fresh signature for each attempt (timestamp changes)
		req, _ := http.NewRequest("POST", baseURL+path, bytes.NewBuffer(orderJSON))
		req.Header.Set("Content-Type", "application/json")
		for k, v := range auth.GetHeaders("POST", path, string(orderJSON)) {
			req.Header.Set(k, v)
		}

		resp, err := http.DefaultClient.Do(req)
		if err != nil {
			fmt.Printf("Attempt %d/%d failed: %v\n", attempt, maxRetries, err)
			if attempt < maxRetries {
				delay := retryDelaySec * (1 << (attempt - 1))
				fmt.Printf("Retrying in %ds...\n", delay)
				time.Sleep(time.Duration(delay) * time.Second)
			}
			continue
		}

		body, _ := io.ReadAll(resp.Body)
		resp.Body.Close()

		if resp.StatusCode >= 200 && resp.StatusCode < 300 {
			var result struct {
				Data struct {
					OrderID string `json:"order_id"`
				} `json:"data"`
			}
			json.Unmarshal(body, &result)
			return BuyResult{Success: true, OrderID: result.Data.OrderID}
		}

		// Parse error message
		var errResp struct {
			Errors struct {
				Detail string `json:"detail"`
			} `json:"errors"`
		}
		json.Unmarshal(body, &errResp)
		errMsg := errResp.Errors.Detail
		if errMsg == "" {
			errMsg = string(body)
		}

		fmt.Printf("Attempt %d/%d failed: %s\n", attempt, maxRetries, errMsg)

		// Don't retry on client errors (4xx) except rate limiting (429)
		if resp.StatusCode >= 400 && resp.StatusCode < 500 && resp.StatusCode != 429 {
			return BuyResult{Success: false, Error: errMsg, Retryable: false}
		}

		// Wait before retrying (exponential backoff)
		if attempt < maxRetries {
			delay := retryDelaySec * (1 << (attempt - 1))
			fmt.Printf("Retrying in %ds...\n", delay)
			time.Sleep(time.Duration(delay) * time.Second)
		}
	}

	return BuyResult{Success: false, Error: "Max retries exceeded", Retryable: true}
}

func main() {
	auth := NewSignatureAuth(
		os.Getenv("GRID_TRADING_PRIVATE_KEY"),
		os.Getenv("GRID_TRADING_PUBLIC_KEY"),
	)

	result := buyWithRetry(
		auth,
		"market_788dcbd5-ac68-4c61-acf1-4443beaf2a1c",
		1,
		2.50,
	)

	if result.Success {
		fmt.Printf("Order placed: %s\n", result.OrderID)
	} else {
		fmt.Printf("Order failed: %s\n", result.Error)
		os.Exit(1)
	}
}

```

{% endtab %}
{% endtabs %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://thegrid.ai/docs/technical-guides/retries-and-error-handling.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
