Skip to content
Last updated

Error Handling

When an API call fails, start with the HTTP status code, then read the JSON error body. MVMNT uses consistent status codes and a consistent error payload to make failures predictable to handle and quick to troubleshoot.

HTTP Status Codes

The MVMNT API uses standard HTTP status codes to indicate the success or failure of requests.

Success Codes

CodeNameUsage
200OKGET, PATCH, and POST /filter operations succeeded
201CreatedPOST (create) operations succeeded
204No ContentDELETE operations succeeded (no response body)

Success Response Examples

201 Created (POST)

POST /v1/vendors

Response:

HTTP/1.1 201 Created
Content-Type: application/json

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "friendlyId": "V100001",
  "name": "ABC Warehouse Services",
  "status": "ACTIVE",
  ...
}

200 OK (GET)

GET /v1/vendors/550e8400-e29b-41d4-a716-446655440000

Response:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "friendlyId": "V100001",
  "name": "ABC Warehouse Services",
  ...
}

200 OK (PATCH)

PATCH /v1/vendors/550e8400-e29b-41d4-a716-446655440000

Response:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "friendlyId": "V100001",
  "name": "ABC Warehouse Services Updated",
  ...
}

204 No Content (DELETE)

DELETE /v1/vendors/550e8400-e29b-41d4-a716-446655440000

Response:

HTTP/1.1 204 No Content

No response body is returned for successful DELETE operations.

Client Error Codes (4xx)

These errors mean the request needs to change before it will succeed.

CodeNameMeaning
400Bad RequestInvalid request format or parameters
401UnauthorizedInvalid or missing authentication token
404Not FoundResource not found
409ConflictDuplicate key or constraint violation
422Unprocessable EntityValidation error
429Too Many RequestsRate limit exceeded

Server Error Codes (5xx)

These errors mean the API couldn’t complete a valid request.

CodeNameMeaning
500Internal Server ErrorUnexpected server error
503Service UnavailableAPI temporarily unavailable (maintenance, etc.)

Error Response Format

All error responses include a JSON body with details about the error:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Validation failed",
    "details": [
      {
        "field": "email",
        "message": "Invalid email format"
      }
    ]
  }
}

Error Response Fields

FieldTypeDescription
error.codestringMachine-readable error code
error.messagestringHuman-readable error message
error.detailsarrayAdditional error details (optional)
error.details[].fieldstringField name that caused the error
error.details[].messagestringField-specific error message

Common Errors

400 Bad Request

Cause: The API can’t parse your request (malformed JSON, invalid parameter types, missing required fields).

Example:

{
  "error": {
    "code": "BAD_REQUEST",
    "message": "Invalid JSON in request body"
  }
}

Troubleshooting:

  • Confirm the request body is valid JSON (no trailing commas, quoted keys/strings).
  • Compare your payload to the endpoint schema and include required fields.
  • Double-check types for query params and JSON fields (string vs number vs boolean).

401 Unauthorized

Cause: The request is missing auth, or the token is expired/invalid.

Example:

{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Invalid or expired access token"
  }
}

Troubleshooting:

  • Request a new access token using /oauth2/token.
  • Send the token on every request: Authorization: Bearer {token}.
  • Verify you’re using the correct client ID and secret for the environment you’re calling.

404 Not Found

Cause: The resource ID doesn’t exist, the endpoint path is wrong, or the resource is no longer available.

Example:

{
  "error": {
    "code": "NOT_FOUND",
    "message": "Vendor not found"
  }
}

Troubleshooting:

  • Verify the ID you’re requesting is correct (copy/paste errors and environment mix-ups are common).
  • Check whether the record was deleted (see Soft Deletes).
  • If you don’t have the ID, use filter endpoints to locate the record by other fields.

409 Conflict

Cause: The request violates a uniqueness constraint (often a duplicate client key).

Example:

{
  "error": {
    "code": "DUPLICATE_KEY",
    "message": "A vendor with this key already exists",
    "details": [
      {
        "field": "key",
        "message": "Key 'ERP-VENDOR-123' is already in use"
      }
    ]
  }
}

Troubleshooting:

  • Generate a unique client key and retry the create.
  • If you intended to upsert, look up the existing record by key first and then PATCH it.
  • If your system retries requests, make sure your key generation is deterministic per record (not per attempt).

422 Unprocessable Entity

Cause: The request is well-formed, but it fails validation rules (missing required values, invalid formats, out-of-range values).

Example:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Validation failed",
    "details": [
      {
        "field": "email",
        "message": "Invalid email format"
      },
      {
        "field": "phone",
        "message": "Phone number is required"
      }
    ]
  }
}

Troubleshooting:

  • Read error.details and fix each field error listed.
  • Validate your payload locally before sending (same rules every time, not “trial and error” against the API).
  • Re-check the API reference for required fields and accepted formats.

429 Too Many Requests

Cause: You hit a rate limit.

Example:

{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Rate limit exceeded. Try again in 30 seconds."
  }
}

Response Headers:

  • X-RateLimit-Limit: Your maximum requests per minute
  • X-RateLimit-Remaining: Requests remaining in current window
  • X-RateLimit-Reset: Unix timestamp when limit resets

Troubleshooting:

  • Implement exponential backoff (and don’t retry in a tight loop).
  • Reduce concurrency and spread requests across time.
  • Batch reads/writes where the API supports it.
  • If your use case needs higher limits, contact support with expected request volume and endpoints.

500 Internal Server Error

Cause: The API encountered an unexpected failure while processing a valid request.

Example:

{
  "error": {
    "code": "INTERNAL_ERROR",
    "message": "An unexpected error occurred"
  }
}

Troubleshooting:

  • Retry with exponential backoff (this is often transient).
  • Log the response body and the X-Request-Id header, then contact support if it repeats.
  • Check the status page for ongoing incidents.

503 Service Unavailable

Cause: The API is temporarily unavailable (maintenance, deploy, or a short outage).

Troubleshooting:

  • Retry with exponential backoff.
  • If you have background jobs, pause/resume rather than failing the whole pipeline on the first 503.
  • Check the status page for maintenance windows or incidents.

Error Handling Best Practices

Do

  • Check the HTTP status code before parsing the response body.
  • Parse error.code for branching logic and error.details for field-level fixes.
  • Retry only transient failures (typically 429, 500, 503) with exponential backoff.
  • Log enough context to reproduce: method, URL, status, response body, and request ID.
  • Translate API errors into specific messages for broker ops (what failed, what to change).

Don’t

  • Don’t retry 4xx errors that require a different request (except 429).
  • Don’t ignore error.details when validation fails; it tells you exactly what to fix.
  • Don’t retry immediately; backoff prevents thundering herds and repeated rate-limit hits.
  • Don’t show raw API payloads to end users; map them to actionable UI text.
  • Don’t keep calling the API after a 401 until you’ve refreshed the token.

Retry Strategies

Exponential Backoff

For transient errors (500, 503, 429), implement exponential backoff:

async function requestWithRetry(url, options, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      const response = await fetch(url, options);

      if (response.ok) {
        return response;
      }

      if (response.status >= 400 && response.status < 500) {
        if (response.status === 429) {
          const backoffMs = Math.pow(2, attempt) * 1000;
          await new Promise((resolve) => setTimeout(resolve, backoffMs));
          continue;
        }
        throw new Error(`Client error: ${response.status}`);
      }

      if (response.status >= 500) {
        const backoffMs = Math.pow(2, attempt) * 1000;
        await new Promise((resolve) => setTimeout(resolve, backoffMs));
        continue;
      }
    } catch (error) {
      if (attempt < maxRetries - 1) {
        const backoffMs = Math.pow(2, attempt) * 1000;
        await new Promise((resolve) => setTimeout(resolve, backoffMs));
        continue;
      }
      throw error;
    }
  }

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

Python Example

import time
import requests
from typing import Optional

def request_with_retry(
    url: str,
    method: str = 'GET',
    max_retries: int = 3,
    **kwargs
) -> requests.Response:
    """Make HTTP request with exponential backoff retry"""
    for attempt in range(max_retries):
        try:
            response = requests.request(method, url, **kwargs)

            # Success
            if response.ok:
                return response

            # Client errors - don't retry (except 429)
            if 400 <= response.status_code < 500:
                if response.status_code == 429:
                    # Rate limit - use exponential backoff
                    backoff_seconds = 2 ** attempt
                    time.sleep(backoff_seconds)
                    continue
                # Other 4xx errors - raise immediately
                response.raise_for_status()

            # Server errors - retry with backoff
            if response.status_code >= 500:
                backoff_seconds = 2 ** attempt
                time.sleep(backoff_seconds)
                continue

        except requests.RequestException as e:
            # Network error - retry with backoff
            if attempt < max_retries - 1:
                backoff_seconds = 2 ** attempt
                time.sleep(backoff_seconds)
                continue
            raise

    raise Exception('Max retries exceeded')

Debugging Tips

1. Log Full Request and Response

Log enough to reproduce the issue and to help support find it quickly:

console.error('Request failed:', {
  url,
  method: options?.method ?? 'GET',
  status: response.status,
  statusText: response.statusText,
  requestId: response.headers.get('X-Request-Id'),
  responseBody: await response.text(),
});

2. Check Response Headers

Useful headers for debugging:

  • X-Request-Id: Unique request identifier for support
  • X-RateLimit-*: Rate limit information
  • Content-Type: Response content type

3. Validate Request Before Sending

Validate your request locally before sending:

function validateVendorInput(vendor) {
  const errors = [];

  if (!vendor.name) {
    errors.push({ field: 'name', message: 'Name is required' });
  }

  if (vendor.email && !isValidEmail(vendor.email)) {
    errors.push({ field: 'email', message: 'Invalid email format' });
  }

  if (errors.length > 0) {
    throw new ValidationError('Invalid input', errors);
  }
}

4. Test Error Scenarios

Test your error handling with scenarios you’ll see in production:

  • Invalid authentication token (401)
  • Nonexistent resource ID (404)
  • Duplicate client key (409)
  • Invalid field values (422)
  • Rate limit exceeded (429)

Next Steps