NetStacksNetStacks

Error Codes

Complete reference for NetStacks API error responses including HTTP status codes, error formats, and resolution steps.

Overview

The NetStacks API returns consistent JSON error responses for all error conditions. Every error includes an HTTP status code, an error code string, a human-readable message, and optional details for field-level validation errors.

Understanding the error format lets you build robust integrations that handle failures gracefully, display meaningful messages to users, and implement automatic retry logic for transient errors.

Error Categories

Errors fall into five categories: authentication (401/403), validation (400/422), not found (404), conflict (409), and server (429/500/503). Each category has different handling strategies.

How It Works

Error Response Format

All API errors return a JSON object with the following structure:

error-format.jsonjson
{
  "error": {
    "code": "DEVICE_NOT_FOUND",
    "message": "Device with ID 'f47ac10b-58cc-4372-a567-0e02b2c3d479' not found",
    "status": 404,
    "details": null
  }
}

Validation Error Details

For 422 validation errors, the details field contains field-level information:

validation-error.jsonjson
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed",
    "status": 422,
    "details": {
      "field": "device_type",
      "value": "invalid_type",
      "message": "Unknown device type 'invalid_type'"
    }
  }
}

Error Codes Reference

HTTP StatusError CodeDescriptionResolution
400INVALID_REQUESTMalformed request body or invalid JSONCheck JSON syntax and Content-Type header
400BAD_REQUESTInvalid parameter value (e.g., bad cron expression)Check the error message for the specific invalid parameter
401TOKEN_EXPIREDAccess token has expiredCall /api/auth/refresh to get a new token
401INVALID_TOKENToken is invalid, malformed, or revokedRe-authenticate via /api/auth/login
401UNAUTHORIZEDNo authentication token providedInclude Authorization: Bearer header
403INSUFFICIENT_PERMISSIONSUser lacks the required permissionCheck role assignments in Admin > Users
404DEVICE_NOT_FOUNDDevice UUID does not existVerify the device ID with GET /api/devices
404TEMPLATE_NOT_FOUNDTemplate UUID does not existVerify the template ID with GET /api/templates
404TASK_NOT_FOUNDScheduled task UUID does not existVerify the task ID with GET /api/tasks/schedules
409DUPLICATE_NAMEResource name already existsUse a unique name or update the existing resource
422VALIDATION_ERRORRequest failed field-level validationCheck error.details for the invalid field
422TEMPLATE_RENDER_ERRORJinja2 template rendering failedCheck template syntax and provide all required variables
429RATE_LIMITEDToo many requestsWait and retry with exponential backoff
500INTERNAL_ERRORUnexpected server errorCheck server logs, contact support
503SERVICE_UNAVAILABLEServer is starting or overloadedRetry with exponential backoff

Step-by-Step Guide

How to Handle API Errors

  1. Check the HTTP status code — Use the status code to determine the error category (4xx = client error, 5xx = server error).
  2. Parse the error JSON — Read the error.code and error.message fields for specific error identification.
  3. Switch on error.code — Handle specific error codes differently: refresh tokens on TOKEN_EXPIRED, fix input on VALIDATION_ERROR, retry on RATE_LIMITED.
  4. Check error.details — For validation errors, the details field contains the specific field name, invalid value, and a descriptive message.
  5. Implement retry logic — For 429 and 503 errors, use exponential backoff: wait 1s, 2s, 4s, 8s up to a maximum of 30 seconds.
  6. Log for debugging — Always log the full error response body for debugging. Include the request ID if available.

Code Examples

Error Responses (curl)

error-examples.shbash
# 401 Unauthorized - expired token
curl -i https://netstacks.example.net/api/devices \
  -H "Authorization: Bearer expired-token-here"

# HTTP/1.1 401 Unauthorized
# {
#   "error": {
#     "code": "TOKEN_EXPIRED",
#     "message": "Access token has expired",
#     "status": 401,
#     "details": null
#   }
# }

# 422 Validation Error - invalid device type
curl -i -X POST https://netstacks.example.net/api/devices \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{"name": "test", "host": "10.0.1.1", "port": 22, "device_type": "not_real"}'

# HTTP/1.1 422 Unprocessable Entity
# {
#   "error": {
#     "code": "VALIDATION_ERROR",
#     "message": "Request validation failed",
#     "status": 422,
#     "details": {"field": "device_type", "value": "not_real", "message": "Unknown device type"}
#   }
# }

# 429 Rate Limited
# HTTP/1.1 429 Too Many Requests
# Retry-After: 60
# {
#   "error": {
#     "code": "RATE_LIMITED",
#     "message": "Too many requests, please try again later",
#     "status": 429,
#     "details": null
#   }
# }

Python Error Handling with Retry

error-handling.pypython
import requests
import time

class NetStacksApiError(Exception):
    def __init__(self, status: int, code: str, message: str, details=None):
        self.status = status
        self.code = code
        self.message = message
        self.details = details
        super().__init__(f"[{status}] {code}: {message}")

def api_request(method: str, url: str, headers: dict, max_retries=3, **kwargs):
    """Make an API request with automatic retry for transient errors."""
    for attempt in range(max_retries + 1):
        resp = requests.request(method, url, headers=headers, **kwargs)

        if resp.ok:
            return resp.json() if resp.content else None

        error_data = resp.json().get("error", {})
        code = error_data.get("code", "UNKNOWN")

        # Retry on rate limit or server errors
        if resp.status_code in (429, 503) and attempt < max_retries:
            wait = min(2 ** attempt, 30)
            print(f"Retrying in {wait}s ({code})...")
            time.sleep(wait)
            continue

        # Token expired - could auto-refresh here
        if code == "TOKEN_EXPIRED":
            raise NetStacksApiError(401, code, "Token expired - refresh needed")

        raise NetStacksApiError(
            resp.status_code, code,
            error_data.get("message", "Unknown error"),
            error_data.get("details")
        )

# Usage
try:
    devices = api_request("GET", f"{base_url}/devices", headers=headers)
except NetStacksApiError as e:
    if e.code == "VALIDATION_ERROR":
        print(f"Validation failed on field: {e.details.get('field')}")
    else:
        print(f"API error: {e}")

TypeScript Error Handling with Retry

error-handling.tstypescript
interface ApiError {
  error: {
    code: string;
    message: string;
    status: number;
    details?: { field?: string; value?: string; message?: string } | null;
  };
}

class NetStacksError extends Error {
  constructor(
    public status: number,
    public code: string,
    public details?: ApiError["error"]["details"]
  ) {
    super(`[${status}] ${code}`);
  }
}

async function apiRequest<T>(
  method: string,
  url: string,
  headers: Record<string, string>,
  options?: { body?: string; maxRetries?: number }
): Promise<T> {
  const maxRetries = options?.maxRetries ?? 3;

  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    const resp = await fetch(url, { method, headers, body: options?.body });

    if (resp.ok) {
      return resp.status === 204 ? (null as T) : resp.json();
    }

    const errorData: ApiError = await resp.json();
    const { code, details } = errorData.error;

    // Retry on transient errors
    if ((resp.status === 429 || resp.status === 503) && attempt < maxRetries) {
      const wait = Math.min(2 ** attempt * 1000, 30000);
      console.warn(`Retrying in ${wait}ms (${code})...`);
      await new Promise((r) => setTimeout(r, wait));
      continue;
    }

    throw new NetStacksError(resp.status, code, details);
  }

  throw new Error("Max retries exceeded");
}

// Usage
try {
  const devices = await apiRequest("GET", `${BASE_URL}/devices`, headers);
} catch (e) {
  if (e instanceof NetStacksError) {
    if (e.code === "TOKEN_EXPIRED") {
      console.log("Refreshing token...");
    } else if (e.code === "VALIDATION_ERROR") {
      console.log(`Invalid field: ${e.details?.field}`);
    }
  }
}

Questions & Answers

What format do API errors return?
All errors return a JSON object with an error field containing code (string), message (human-readable), status (HTTP status code), and optional details (field-level info for validation errors).
How do I handle token expiration?
When you receive a 401 with code TOKEN_EXPIRED, call POST /api/auth/refresh with your refresh token to get a new access token. If the refresh token is also expired, re-authenticate via /api/auth/login.
What does a 422 validation error look like?
A 422 error includes a details field with the specific field name that failed validation, the invalid value, and a descriptive message. Use this to display field-specific error messages in your UI.
How should I handle rate limiting?
When you receive a 429 response, implement exponential backoff: wait 1 second, then 2, then 4, up to 30 seconds max. The response may include a Retry-After header with the recommended wait time.
How do I debug a 500 Internal Server Error?
A 500 error indicates an unexpected server-side issue. Check the Controller logs for the full error trace. If the error persists, report it with the request details (method, URL, body) and the error response body.
Are error codes stable across API versions?
Yes. Error codes like DEVICE_NOT_FOUND, VALIDATION_ERROR, and TOKEN_EXPIRED are stable and safe to match on in your code. New codes may be added in future versions but existing codes will not change.

Troubleshooting

Getting 401 on Every Request

The token is either not included, expired, or malformed. Verify the Authorization: Bearer <token> header is present and the token is a valid JWT. Use the auth endpoint to get a fresh token.

403 After Token Refresh

The user's role or permissions were changed after the original token was issued. The refreshed token reflects the current permissions. Check if the user's roles still include the required permission for the operation.

422 on Device Creation

Check that all required fields are provided: name, host, port, and device_type. The error.details field identifies exactly which field failed validation and why.

429 in Automated Scripts

Your script is sending requests too quickly. Add rate limiting to your client: space requests at least 100ms apart and implement exponential backoff on 429 responses. Consider batching operations where possible.

500 Errors During Deployments

Internal errors during stack deployments may indicate database connectivity issues, SSH connection failures, or resource exhaustion. Check the Controller logs and the deployment status endpoint for device-level error details.