Skip to content

Error Handling

This guide explains how the SynthesQ API handles and reports errors, including error response formats, validation errors, and best practices for error handling in your application.

Error Response Format

All error responses follow a consistent JSON structure to make error handling predictable.

Standard Error Response

json
{
  "error": "Error type or title",
  "message": "Human-readable error description",
  "errors": {
    "field_name": [
      "Specific error message"
    ]
  }
}

HTTP Status Codes

The API uses standard HTTP status codes to indicate the type of error:

Client Errors (4xx)

400 Bad Request

The request was malformed or contains invalid syntax.

Example:

json
{
  "error": "Bad Request",
  "message": "The request could not be understood by the server"
}

Common causes:

  • Invalid JSON syntax
  • Missing required headers
  • Malformed URL parameters

401 Unauthorized

Authentication is required or has failed.

Example:

json
{
  "error": "Unauthorized",
  "message": "Unauthenticated."
}

Common causes:

  • Missing Authorization header
  • Invalid or expired access token
  • Revoked authentication credentials

Solution:

bash
# Include valid Bearer token
curl https://api.synthesq.com/api/v1/operations/products \
  -H "Authorization: Bearer YOUR_VALID_TOKEN"

403 Forbidden

The request is valid, but you don't have permission to perform this action.

Example:

json
{
  "error": "Forbidden",
  "message": "You do not have permission to perform this action"
}

Common causes:

  • Insufficient user permissions/roles
  • Trying to access another tenant's data
  • Action not allowed for your account type

404 Not Found

The requested resource doesn't exist.

Example:

json
{
  "error": "Not Found",
  "message": "Resource not found"
}

Common causes:

  • Invalid resource ID
  • Resource was deleted
  • Incorrect endpoint URL

Example:

bash
# Invalid product ID
GET /api/v1/operations/products/99999

# Response
{
  "error": "Not Found",
  "message": "Product not found"
}

422 Unprocessable Entity

The request was well-formed but contains validation errors.

Example:

json
{
  "errors": {
    "name": [
      "Product name is required"
    ],
    "sku": [
      "SKU already exists"
    ],
    "selling_price": [
      "Selling price must be a valid number",
      "Selling price cannot be negative"
    ],
    "cost_price": [
      "Cost price should not be higher than selling price (negative margin)"
    ],
    "reserved_quantity": [
      "Reserved quantity cannot exceed stock quantity"
    ]
  }
}

Common validation errors:

Field ErrorDescription
requiredField is required but missing
uniqueValue must be unique (e.g., SKU, email)
existsReferenced resource doesn't exist (e.g., category_id)
min / maxValue outside allowed range
numericValue must be a number
emailInvalid email format
urlInvalid URL format

429 Too Many Requests

You've exceeded the rate limit.

Example:

json
{
  "message": "Too many requests. Please try again later.",
  "retry_after": 45
}

Response headers:

http
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1642598400
Retry-After: 45

Solution: Wait for the time specified in retry_after (seconds) or Retry-After header before making another request.

Server Errors (5xx)

500 Internal Server Error

An unexpected error occurred on the server.

Example:

json
{
  "error": "Failed to create product",
  "message": "An unexpected error occurred. Please try again later."
}

In development mode (app.debug=true), additional details may be included:

json
{
  "error": "Failed to create product",
  "message": "SQLSTATE[23000]: Integrity constraint violation",
  "errors": {
    "exception": "PDOException: SQLSTATE[23000]...",
    "trace": "Stack trace..."
  }
}

Production Note

Detailed error traces are only shown in development environments. Production responses will hide sensitive implementation details.

503 Service Unavailable

The service is temporarily unavailable (maintenance, overload, etc.).

Example:

json
{
  "error": "Service Unavailable",
  "message": "The service is temporarily unavailable. Please try again later."
}

Validation Errors in Detail

Field-Level Validation

Validation errors are returned with the specific field that failed validation:

Request:

bash
POST /api/v1/operations/products
{
  "name": "",
  "sku": "EXISTING-SKU",
  "selling_price": -100
}

Response (422):

json
{
  "errors": {
    "name": [
      "Product name is required"
    ],
    "sku": [
      "SKU already exists"
    ],
    "selling_price": [
      "Selling price cannot be negative"
    ]
  }
}

Business Rule Validation

Some errors represent business rule violations:

Request:

json
{
  "name": "Test Product",
  "sku": "TEST-001",
  "selling_price": 100.00,
  "cost_price": 150.00,
  "stock_quantity": 50,
  "reserved_quantity": 75
}

Response (422):

json
{
  "errors": {
    "cost_price": [
      "Cost price should not be higher than selling price (negative margin)"
    ],
    "reserved_quantity": [
      "Reserved quantity cannot exceed stock quantity"
    ]
  }
}

Nested Field Validation

For nested objects (arrays, JSON fields), errors are dot-notated:

Request:

json
{
  "name": "Product",
  "sku": "TEST-001",
  "selling_price": 100,
  "images": [
    {
      "url": "not-a-url",
      "is_primary": true
    },
    {
      "url": "also-not-a-url",
      "is_primary": true
    }
  ],
  "attributes": [
    {
      "code": "non-existent-attribute",
      "value": "test"
    }
  ]
}

Response (422):

json
{
  "errors": {
    "images.0.url": [
      "The images.0.url must be a valid URL"
    ],
    "images.1.url": [
      "The images.1.url must be a valid URL"
    ],
    "images": [
      "Only one image can be set as primary"
    ],
    "attributes.0.code": [
      "The specified attribute does not exist"
    ]
  }
}

Handling Errors in Your Application

Basic Error Handling

javascript
async function createProduct(data) {
  try {
    const response = await fetch('https://api.synthesq.com/api/v1/operations/products', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json',
        'Accept': 'application/json'
      },
      body: JSON.stringify(data)
    });

    // Check if response is successful
    if (!response.ok) {
      const error = await response.json();
      throw new Error(error.message || 'Request failed');
    }

    return await response.json();

  } catch (error) {
    console.error('Error creating product:', error);
    throw error;
  }
}

Handling Validation Errors

javascript
async function createProduct(data) {
  try {
    const response = await fetch('https://api.synthesq.com/api/v1/operations/products', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(data)
    });

    if (response.status === 422) {
      const { errors } = await response.json();

      // Display validation errors to user
      Object.keys(errors).forEach(field => {
        errors[field].forEach(message => {
          console.error(`${field}: ${message}`);
          // Display in UI: showFieldError(field, message);
        });
      });

      return { success: false, errors };
    }

    if (!response.ok) {
      throw new Error('Request failed');
    }

    const result = await response.json();
    return { success: true, data: result.data };

  } catch (error) {
    console.error('Unexpected error:', error);
    return { success: false, error: error.message };
  }
}

Retry Logic for Rate Limiting

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

      // Handle rate limiting
      if (response.status === 429) {
        const retryAfter = response.headers.get('Retry-After') || 60;

        if (attempt < maxRetries) {
          console.log(`Rate limited. Retrying after ${retryAfter} seconds...`);
          await sleep(retryAfter * 1000);
          continue;
        }

        throw new Error('Rate limit exceeded');
      }

      return response;

    } catch (error) {
      if (attempt === maxRetries) throw error;

      // Exponential backoff for server errors
      const delay = Math.pow(2, attempt) * 1000;
      await sleep(delay);
    }
  }
}

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

Best Practices

1. Always Check Status Codes

Don't assume a request succeeded. Always check the HTTP status code.

javascript
if (!response.ok) {
  // Handle error
}

2. Handle Validation Errors Gracefully

Display field-specific validation errors to users in a helpful way.

javascript
// Good: Show error next to the field
showFieldError('sku', 'SKU already exists');

// Bad: Generic alert
alert('Something went wrong');

3. Implement Retry Logic

For transient errors (500, 503) and rate limiting (429), implement retry logic with exponential backoff.

4. Log Errors for Debugging

Log full error responses (in development) to help diagnose issues:

javascript
console.error('API Error:', {
  status: response.status,
  body: await response.json(),
  url: response.url
});

5. Handle Token Expiration

When you receive a 401 Unauthorized, refresh the access token or redirect to login:

javascript
if (response.status === 401) {
  // Token expired - refresh or re-authenticate
  await refreshToken();
  // Retry the original request
}

6. Provide User-Friendly Messages

Translate technical errors into user-friendly messages:

javascript
const userFriendlyMessages = {
  401: 'Please log in again',
  403: 'You don\'t have permission to do this',
  404: 'Item not found',
  422: 'Please check your input',
  500: 'Something went wrong. Please try again later',
  503: 'Service is temporarily unavailable'
};

const message = userFriendlyMessages[response.status] || 'An error occurred';

Next Steps

Documentation for SynthesQ CRM/ERP Platform