Skip to main content
Crusoe Support Help Center home page
Crusoe

How-To Check GPU Capacity Using Crusoe Cloud API

Sagar Lulla
Sagar Lulla
Updated

Introduction

This guide shows you how to query available GPU capacity on Crusoe Cloud using the REST API. Checking capacity programmatically allows you to automate resource planning, monitor availability across regions, and integrate capacity checks into your deployment workflows. By following this guide, you'll be able to retrieve real-time information about available GPU instances across different locations and VM types.

Prerequisites

Before starting, ensure you have:

  • API Credentials: Valid Crusoe Cloud API access key and secret key
  • Python Environment: Python 3.6+ with the following packages:
    • requests
    • hmac (built-in)
    • hashlib (built-in)
    • base64 (built-in)
    • datetime (built-in)
  • Network Access: Ability to make HTTPS requests to api.crusoecloud.com
  • Account Permissions: Your API credentials must have permission to query capacity data

Step-by-Step Instructions

1. Set Up Authentication

  • Replace the placeholder API credentials in your code with your actual credentials:

    api_access_key = "your_actual_access_key_here"
    api_secret_key = "your_actual_secret_key_here"
  • Ensure your credentials are in plain text
  • Security Note: Never hardcode credentials in production code. Use environment variables or secure credential management systems

2. Generate the HMAC-SHA256 Signature

  • The Crusoe API requires HMAC-SHA256 authentication for all requests
  • The signature is generated using the request path, query parameters, HTTP verb, and timestamp:

    def generate_signature(path, query, verb, timestamp):    
      payload = f"{path}\n{query}\n{verb}\n{timestamp}\n"    
      decoded = base64.urlsafe_b64decode(api_secret_key + '=' * (-len(api_secret_key) % 4))    
      sig = hmac.new(decoded, msg=payload.encode('ascii'), digestmod=hashlib.sha256).digest()    
      signature = base64.urlsafe_b64encode(sig).decode('ascii').rstrip('=')    
      return signature
  • Important: The timestamp must be in UTC ISO format with no microseconds

3. Build the API Request

  • Construct the request URL using the base API endpoint:

    base_url = "https://api.crusoecloud.com"
    api_version = "/v1alpha5"
    endpoint = "/capacities"
  • Add query parameters for filtering (optional):
    • product_name: Filter by specific GPU types (e.g., 'a100.1x', 'h100.1x')
    • location: Filter by specific regions (e.g., 'us-east-1a', 'eu-iceland1-a')
  • Tip: Omit filters to query all available capacity across all regions and GPU types

4. Set Request Headers

  • Include the required headers for authentication:

    headers = {    
      "X-Crusoe-Timestamp": timestamp,    
      "Authorization": f"Bearer 1.0:{api_access_key}:{signature}",    
      "Content-Type": "application/json",
    }
  • The Authorization header follows the format: Bearer 1.0:access_key:signature

5. Send the Request and Handle the Response

  • Make a GET request to the API endpoint:

    response = requests.get(url, headers=headers, timeout=30)
  • Process the response based on the HTTP status code:
    • 200 OK: Successful query, parse the JSON response
    • 400 Bad Request: Invalid parameters or malformed request
    • 401 Unauthorized: Authentication failed
    • 403 Forbidden: Insufficient permissions
    • 500 Internal Server Error: Server-side issue

6. Parse and Display Capacity Data

  • Extract capacity information from the JSON response:

    data = response.json()
    for item in data['items']:    
      location = item.get('location')    
      vm_type = item.get('type')    
      quantity = item.get('quantity')
  • The response includes available capacity organized by location and VM type
  • Tip: Aggregate data to create summary tables by region or GPU type

Example

Querying All Available Capacity

import hmac
import hashlib
import base64
import datetime
import requests
import json
from urllib.parse import urlencode

# Replace with your actual API credentials
api_access_key = "<Your-Access-Key>"
api_secret_key = "<Your-Secret-Key>"

def generate_signature(path, query, verb, timestamp):
    """
    Generate the HMAC-SHA256 signature for the API request.
    """
    payload = f"{path}\n{query}\n{verb}\n{timestamp}\n"
    print(f"DEBUG - Signature payload: '{payload}'")
    print(f"DEBUG - Path: '{path}'")
    print(f"DEBUG - Query: '{query}'")
    print(f"DEBUG - Verb: '{verb}'")
    print(f"DEBUG - Timestamp: '{timestamp}'")
    
    try:
        decoded = base64.urlsafe_b64decode(api_secret_key + '=' * (-len(api_secret_key) % 4))
        print(f"DEBUG - Secret key decoded successfully, length: {len(decoded)}")
    except Exception as e:
        print(f"DEBUG - Error decoding secret key: {e}")
        return None
        
    sig = hmac.new(decoded, msg=payload.encode('ascii'), digestmod=hashlib.sha256).digest()
    signature = base64.urlsafe_b64encode(sig).decode('ascii').rstrip('=')
    print(f"DEBUG - Generated signature: '{signature}'")
    return signature

def handle_response(response):
    """
    Enhanced error handling based on Crusoe Cloud API response schemas.
    """
    if response.status_code == 200:
        print("HTTP 200 OK - Capacity query succeeded")
        try:
            data = response.json()
            if 'items' in data and data['items']:
                print(f"Found {len(data['items'])} capacity items:")
                print(json.dumps(data, indent=2))
                
                # Summary of capacity
                print("\nCapacity Summary:")
                locations = set()
                types = set()
                total_quantity = 0
                
                # Create capacity table data
                capacity_data = {}
                
                for item in data['items']:
                    location = item.get('location', 'Unknown')
                    vm_type = item.get('type', 'Unknown')
                    quantity = item.get('quantity', 0)
                    
                    locations.add(location)
                    types.add(vm_type)
                    total_quantity += quantity
                    
                    # Build capacity table
                    if location not in capacity_data:
                        capacity_data[location] = {}
                    capacity_data[location][vm_type] = quantity
                
                print(f"   Locations: {len(locations)} ({', '.join(sorted(locations))})")
                print(f"   GPU Types: {len(types)} ({', '.join(sorted(types))})")
                print(f"   Total Available Units: {total_quantity}")
                
                # Create and display capacity table
                print("\n" + "="*100)
                print("CAPACITY TABLE BY REGION AND VM TYPE")
                print("="*100)
                
                # Get all unique VM types for table headers
                all_vm_types = sorted(types)
                all_locations = sorted(locations)
                
                # Print table header
                print(f"{'Region':<20} | {'VM Type':<25} | {'Available Units':<15}")
                print("-" * 65)
                
                # Print capacity data organized by region
                for location in all_locations:
                    first_row = True
                    if location in capacity_data:
                        # Sort VM types by name for consistent ordering
                        location_vms = sorted(capacity_data[location].items())
                        
                        for vm_type, quantity in location_vms:
                            if quantity > 0:  # Only show VM types with available capacity
                                if first_row:
                                    print(f"{location:<20} | {vm_type:<25} | {quantity:<15}")
                                    first_row = False
                                else:
                                    print(f"{'':<20} | {vm_type:<25} | {quantity:<15}")
                    
                    if not first_row:  # Add separator between regions if we printed anything
                        print("-" * 65)
                
                # Summary table by VM type across all regions
                print("\n" + "="*100)
                print("SUMMARY TABLE BY VM TYPE (Total Across All Regions)")
                print("="*100)
                
                vm_totals = {}
                for location_data in capacity_data.values():
                    for vm_type, quantity in location_data.items():
                        if vm_type not in vm_totals:
                            vm_totals[vm_type] = 0
                        vm_totals[vm_type] += quantity
                
                print(f"{'VM Type':<30} | {'Total Available Units':<20}")
                print("-" * 55)
                
                for vm_type in sorted(vm_totals.keys()):
                    if vm_totals[vm_type] > 0:
                        print(f"{vm_type:<30} | {vm_totals[vm_type]:<20}")
                
                print("="*100)
            else:
                print("No capacity items found in response")
                print(json.dumps(data, indent=2))
                
        except (ValueError, KeyError) as e:
            print(f"Error parsing response JSON: {e}")
            print("Raw response:", response.text)
            
    elif response.status_code == 400:
        print("HTTP 400 Bad Request")
        try:
            error_data = response.json()
            code = error_data.get('code', 'unknown_error')
            message = error_data.get('message', 'No error message provided')
            print(f"   Error Code: {code}")
            print(f"   Message: {message}")
            
            # Provide helpful suggestions for common 400 errors
            if 'location' in message.lower():
                print("Suggestion: Check if the location names are correct")
                print("   Valid locations: us-east-1a, us-southcentral-1a, us-northcentral-1a, eu-iceland1-a")
            elif 'product' in message.lower():
                print("Suggestion: Check if the product names are correct")
                print("   Example product types: a100.1x, a100.2x, h100.1x, etc.")
                
        except (ValueError, KeyError):
            print("   Raw response:", response.text)
            
    elif response.status_code == 401:
        print("HTTP 401 Unauthorized - Authentication Failed")
        try:
            error_data = response.json()
            code = error_data.get('code', 'unknown_error')
            message = error_data.get('message', 'No error message provided')
            print(f"   Error Code: {code}")
            print(f"   Message: {message}")
            
            print("Troubleshooting suggestions:")
            print("   • Verify your API access key and secret key are correct")
            print("   • Check if your API credentials have expired")
            print("   • Ensure the timestamp generation is working correctly")
            print("   • Verify the signature generation matches API requirements")
            
        except (ValueError, KeyError):
            print("   Raw response:", response.text)
            
    elif response.status_code == 403:
        print("HTTP 403 Forbidden - Permission Denied")
        try:
            error_data = response.json()
            code = error_data.get('code', 'unknown_error')
            message = error_data.get('message', 'No error message provided')
            print(f"   Error Code: {code}")
            print(f"   Message: {message}")
            
            print("Troubleshooting suggestions:")
            print("   • Your credentials may not have permission to access capacity data")
            print("   • Contact Crusoe Cloud support to verify your account permissions")
            print("   • Check if your account is properly activated")
            
        except (ValueError, KeyError):
            print("   Raw response:", response.text)
            
    elif response.status_code == 500:
        print("HTTP 500 Internal Server Error")
        try:
            error_data = response.json()
            code = error_data.get('code', 'unknown_error')
            message = error_data.get('message', 'No error message provided')
            print(f"   Error Code: {code}")
            print(f"   Message: {message}")
            
            print("This is a server-side issue. Try again in a few minutes.")
            print("   If the problem persists, contact Crusoe Cloud support.")
            
        except (ValueError, KeyError):
            print("   Raw response:", response.text)
            
    else:
        print(f"HTTP {response.status_code} - Unexpected Response")
        print("Raw response:", response.text)
        print("This is an unexpected status code. Contact Crusoe Cloud support if this persists.")

def query_capacity(product_names=None, locations=None):
    """
    Query Crusoe Cloud capacity with optional filters.
    
    Args:
        product_names (list): List of product names to filter by (e.g., ['a100.1x', 'a100.2x'])
        locations (list): List of locations to filter by (e.g., ['us-east-1a', 'us-southcentral-1a'])
    """
    
    base_url = "https://api.crusoecloud.com"
    api_version = "/v1alpha5"
    verb = "GET"
    endpoint = "/capacities"
    
    # Build query parameters
    query_params = {}
    if product_names:
        query_params['product_name'] = product_names
    if locations:
        query_params['location'] = locations
    
    # Convert query parameters to URL-encoded string
    query = urlencode(query_params, doseq=True) if query_params else ""
    
    # Generate timestamp for the request
    ts = datetime.datetime.now(datetime.timezone.utc).replace(microsecond=0).isoformat()
    print(f"DEBUG - Generated timestamp: {ts}")
    
    # Generate the signature
    signature = generate_signature(api_version + endpoint, query, verb, ts)
    
    if signature is None:
        print("ERROR - Failed to generate signature, aborting request")
        return None
    
    # Prepare headers
    auth_header = f"Bearer 1.0:{api_access_key}:{signature}"
    headers = {
        "X-Crusoe-Timestamp": ts,
        "Authorization": auth_header,
        "Content-Type": "application/json",
    }
    
    print(f"DEBUG - Authorization header: '{auth_header}'")
    print(f"DEBUG - All headers: {headers}")
    
    # Build the full URL
    url = f"{base_url}{api_version}{endpoint}"
    if query:
        url += f"?{query}"
    
    print(f"Querying capacity at: {url}")
    
    # Send the GET request
    try:
        response = requests.get(
            url,
            headers=headers,
            timeout=30
        )
        
        print(f"DEBUG - Response status: {response.status_code}")
        print(f"DEBUG - Response headers: {dict(response.headers)}")
        
        # Handle the response
        handle_response(response)
        
        return response
        
    except requests.exceptions.Timeout:
        print("Request timed out. The API might be slow or unavailable.")
        print("Try again in a few minutes.")
        
    except requests.exceptions.ConnectionError:
        print("Connection error. Check your internet connection.")
        print("Verify the API endpoint URL is correct.")
        
    except requests.exceptions.RequestException as e:
        print(f"Request failed with error: {e}")
        print("Check your network connection and try again.")
        
    except Exception as e:
        print(f"Unexpected error occurred: {e}")
        print("This might be a code issue. Check your implementation.")

def query_all_capacity():
    """
    Query all available Crusoe Cloud capacity (all GPU types, all locations).
    """
    print("Querying all available capacity (all GPU types, all locations)...")
    query_capacity()

def main():
    """
    Main function to query all available capacity.
    """
    print("=== Crusoe Cloud Capacity Query ===\n")
    
    # Query all capacity
    print("Querying all available capacity (all GPU types, all locations)...")
    query_all_capacity()
    
    print("\nQuery completed!")

if __name__ == "__main__":
    main()

Sample Response Output

.
.
Capacity Summary:
   Locations: 4 (eu-iceland1-a, us-east1-a, us-southcentral1-a, us-west1-a)
   GPU Types: 30 (a100-80gb-sxm-ib.8x, a100-80gb.1x, a100-80gb.2x, a100-80gb.4x, a100-80gb.8x, b200-180gb-sxm-ib.8x, c1a.128x, c1a.16x, c1a.176x, c1a.2x, c1a.32x, c1a.4x, c1a.64x, c1a.8x, gb200-186gb-nvl-ib.4x, gb200-186gb-nvl.4x, h100-80gb-sxm-ib.8x, h200-141gb-sxm-ib.8x, l40s-48gb.10x, l40s-48gb.1x, l40s-48gb.2x, l40s-48gb.4x, l40s-48gb.8x, mi300x-192gb-ib.8x, s1a.120x, s1a.160x, s1a.20x, s1a.40x, s1a.60x, s1a.80x)
   Total Available Units: 45133

====================================================================================================
CAPACITY TABLE BY REGION AND VM TYPE
====================================================================================================
Region               | VM Type                   | Available Units
-----------------------------------------------------------------
eu-iceland1-a        | b200-180gb-sxm-ib.8x      | 2
                     | c1a.128x                  | 43
                     | c1a.16x                   | 453
                     | c1a.176x                  | 33
                     | c1a.2x                    | 3654
                     | c1a.32x                   | 223
                     | c1a.4x                    | 1826
                     | c1a.64x                   | 104
                     | c1a.8x                    | 911
                     | gb200-186gb-nvl-ib.4x     | 36
                     | gb200-186gb-nvl.4x        | 2
                     | h100-80gb-sxm-ib.8x       | 4
                     | h200-141gb-sxm-ib.8x      | 62
                     | s1a.120x                  | 10
                     | s1a.160x                  | 8
                     | s1a.20x                   | 81
                     | s1a.40x                   | 39
                     | s1a.60x                   | 20
                     | s1a.80x                   | 18
-----------------------------------------------------------------
us-east1-a           | a100-80gb-sxm-ib.8x       | 22
                     | a100-80gb.1x              | 128
                     | a100-80gb.2x              | 62
                     | a100-80gb.4x              | 28
                     | a100-80gb.8x              | 12
                     | c1a.128x                  | 10
                     | c1a.16x                   | 109
                     | c1a.176x                  | 7
                     | c1a.2x                    | 901
                     | c1a.32x                   | 54
                     | c1a.4x                    | 450
                     | c1a.64x                   | 25
                     | c1a.8x                    | 223
                     | h100-80gb-sxm-ib.8x       | 30
                     | l40s-48gb.10x             | 58
                     | l40s-48gb.1x              | 707
                     | l40s-48gb.2x              | 346
                     | l40s-48gb.4x              | 141
                     | l40s-48gb.8x              | 66
                     | mi300x-192gb-ib.8x        | 4
                     | s1a.120x                  | 2
                     | s1a.20x                   | 27
                     | s1a.40x                   | 11
                     | s1a.60x                   | 7
                     | s1a.80x                   | 4
-----------------------------------------------------------------
us-southcentral1-a   | c1a.128x                  | 5
                     | c1a.16x                   | 68
                     | c1a.176x                  | 5
                     | c1a.2x                    | 561
                     | c1a.32x                   | 33
                     | c1a.4x                    | 279
                     | c1a.64x                   | 14
                     | c1a.8x                    | 139
                     | h100-80gb-sxm-ib.8x       | 17
                     | l40s-48gb.10x             | 4
                     | l40s-48gb.1x              | 90
                     | l40s-48gb.2x              | 42
                     | l40s-48gb.4x              | 15
                     | l40s-48gb.8x              | 6
-----------------------------------------------------------------

====================================================================================================
SUMMARY TABLE BY VM TYPE (Total Across All Regions)
====================================================================================================
VM Type                        | Total Available Units
-------------------------------------------------------
a100-80gb-sxm-ib.8x            | 22
a100-80gb.1x                   | 128
a100-80gb.2x                   | 62
a100-80gb.4x                   | 28
a100-80gb.8x                   | 12
b200-180gb-sxm-ib.8x           | 2
c1a.128x                       | 58
c1a.16x                        | 630
c1a.176x                       | 45
c1a.2x                         | 5116
c1a.32x                        | 310
c1a.4x                         | 2555
c1a.64x                        | 143
c1a.8x                         | 1273
gb200-186gb-nvl-ib.4x          | 36
gb200-186gb-nvl.4x             | 2
h100-80gb-sxm-ib.8x            | 51
h200-141gb-sxm-ib.8x           | 62
l40s-48gb.10x                  | 62
l40s-48gb.1x                   | 797
l40s-48gb.2x                   | 388
l40s-48gb.4x                   | 156
l40s-48gb.8x                   | 72
mi300x-192gb-ib.8x             | 4
s1a.120x                       | 12
s1a.160x                       | 8
s1a.20x                        | 108
s1a.40x                        | 50
s1a.60x                        | 27
s1a.80x                        | 22
====================================================================================================

Troubleshooting

401 Unauthorized Error

  • Problem: Authentication failed
  • Solution:
    • Verify your API access key and secret key are correct
    • Check if your API credentials have expired
    • Ensure the timestamp is generated correctly in UTC format
    • Verify the signature generation matches API requirements

400 Bad Request Error

  • Problem: Invalid request parameters
  • Solution:
    • Check that location names are valid (e.g., 'us-east-1a', 'us-southcentral-1a', 'us-northcentral-1a', 'eu-iceland1-a')
    • Verify product names are correct (e.g., 'a100.1x', 'a100.2x', 'h100.1x')
    • Ensure query parameters are properly URL-encoded

403 Forbidden Error

  • Problem: Insufficient permissions
  • Solution:
    • Your credentials may not have permission to access capacity data
    • Contact Crusoe Cloud support to verify your account permissions
    • Check if your account is properly activated

Empty Response or No Capacity Items

  • Problem: No capacity data returned
  • Solution:
    • Try querying without filters to see all available capacity
    • Check if the regions you're querying actually have GPU capacity
    • Verify the API version is correct (/v1alpha5)

Signature Generation Issues

  • Problem: Authentication fails due to incorrect signature
  • Solution:
    • Ensure the secret key is properly base64-decoded before signing
    • Verify the payload format matches exactly: path\nquery\nverb\ntimestamp\n
    • Check that the timestamp format is correct (ISO format, UTC, no microseconds)

Additional Resources

Related to

Was this article helpful?

0 out of 0 found this helpful

Still need help?

Our support team is ready to assist you with any questions.

Have more questions? Submit a request

Recently Viewed

Comments

0 comments

Article is closed for comments.