SyntaxdocSyntaxdoc

Error Handling

Understanding and handling API errors from SyntaxDoc

Learn how to handle errors from the SyntaxDoc API.

Error Response Format

All errors return a JSON response with the following structure:

{
  "statusCode": 400,
  "code": "BAD_REQUEST",
  "message": "Human-readable error message",
  "hint": "Optional hint for fixing the error"
}

Or the simpler format:

{
  "success": false,
  "message": "Human-readable error message"
}

HTTP Status Codes

StatusDescription
200Success - PDF returned or JSON response
400Bad Request - Invalid or missing parameters
401Unauthorized - Invalid or missing API key
403Forbidden - Access denied to resource
404Not Found - PDF or resource doesn't exist
429Too Many Requests - Rate/quota limit exceeded
500Internal Server Error - Try again later

Error Codes Reference

Authentication Errors

UNAUTHORIZED

Status: 401

{
  "success": false,
  "message": "API key is required. Please provide it in X-API-Key header or Authorization header."
}

Causes:

  • No API key provided
  • Invalid API key format
  • API key has been revoked or deleted

Solution: Ensure you're sending the API key correctly:

# Header format
X-API-Key: your_api_key_here

# Or Bearer token format
Authorization: Bearer your_api_key_here

Invalid API Key

{
  "success": false,
  "message": "Invalid or expired API key."
}

Causes:

  • API key doesn't exist
  • API key has expired
  • API key belongs to a deleted user

Solution: Generate a new API key from the dashboard.


Request Errors

BAD_REQUEST

Status: 400

No Files Uploaded:

{
  "statusCode": 400,
  "code": "BAD_REQUEST",
  "message": "No files uploaded. Please upload at least one HTML file with key \"files\".",
  "hint": "In Postman, ensure you select an actual file from your file system in the form-data body."
}

No HTML File Found:

{
  "statusCode": 400,
  "code": "BAD_REQUEST",
  "message": "No HTML file found in the request. Expected a file named \"index.html\" or any .html file.",
  "receivedFiles": [{"name": "document.txt", "mimetype": "text/plain"}],
  "hint": "Make sure to select an HTML file from your file system."
}

No PDF Files (Merge):

{
  "statusCode": 400,
  "code": "BAD_REQUEST",
  "message": "No PDF files provided for merging"
}

Quota & Limit Errors

QUOTA_EXCEEDED

Status: 429

{
  "statusCode": 429,
  "code": "QUOTA_EXCEEDED",
  "message": "You have reached your conversion limit for this billing period"
}

Causes:

  • Monthly conversion limit reached for your plan

Solution:

  • Wait for the next billing period
  • Upgrade to a higher plan
  • Check usage: GET /api/billing/usage

HOSTED_PDF_LIMIT_REACHED

Status: 429

{
  "statusCode": 429,
  "code": "HOSTED_PDF_LIMIT_REACHED",
  "message": "Hosted PDF limit reached. Delete existing PDFs or upgrade your plan."
}

Causes:

  • Reached the maximum number of hosted PDFs for your plan

Limits by Plan:

PlanHosted PDF Limit
Hobby10
Pro500
Enterprise10,000

Solution:

  • Delete unused hosted PDFs: DELETE /api/hosted-pdfs/:pdfId
  • Upgrade your plan: POST /api/billing/subscription/change-plan

Resource Errors

NOT_FOUND

Status: 404

{
  "statusCode": 404,
  "code": "NOT_FOUND",
  "message": "File not found or expired"
}

Causes:

  • PDF ID doesn't exist
  • PDF has expired (exceeded retention period)
  • PDF was deleted
  • Invalid file ID format

Retention by Plan:

PlanRetention Period
Hobby1 day
Pro7 days
Enterprise30 days

FORBIDDEN

Status: 403

{
  "statusCode": 403,
  "code": "FORBIDDEN",
  "message": "Access denied"
}

Causes:

  • Trying to access another user's PDF
  • PDF sharing is not enabled
  • Insufficient permissions

Server Errors

INTERNAL_SERVER_ERROR

Status: 500

{
  "statusCode": 500,
  "code": "INTERNAL_SERVER_ERROR",
  "message": "PDF generation failed"
}

Causes:

  • Server-side processing error
  • HTML rendering failure
  • Resource timeout

Solution:

  • Check your HTML for JavaScript errors
  • Ensure external resources (fonts, images) are accessible
  • Retry the request
  • If persistent, contact support

Handling Errors in Code

async function generatePdf(htmlFile) {
  const formData = new FormData();
  formData.append('files', htmlFile);
  formData.append('options', JSON.stringify({ fileName: 'document' }));

  try {
    const response = await fetch('https://api.syntaxdoc.com/pdf/generate', {
      method: 'POST',
      headers: { 'X-API-Key': 'YOUR_API_KEY' },
      body: formData
    });

    // Check if response is an error
    if (!response.ok) {
      const error = await response.json();
      
      switch (response.status) {
        case 401:
          throw new Error('Invalid API key. Check your credentials.');
        
        case 429:
          if (error.code === 'HOSTED_PDF_LIMIT_REACHED') {
            throw new Error('Hosted PDF limit reached. Delete some PDFs first.');
          }
          throw new Error('Conversion quota exceeded. Upgrade your plan.');
        
        case 400:
          throw new Error(`Bad request: ${error.message}`);
        
        default:
          throw new Error(error.message || 'Unknown error occurred');
      }
    }

    // Check content type for success response
    const contentType = response.headers.get('content-type');
    
    if (contentType?.includes('application/json')) {
      // Hosted PDF response
      return await response.json();
    } else {
      // Direct PDF binary
      return await response.blob();
    }
  } catch (error) {
    console.error('PDF generation failed:', error.message);
    throw error;
  }
}
import requests
import json

class SyntaxDocError(Exception):
    def __init__(self, code, message, status_code):
        self.code = code
        self.message = message
        self.status_code = status_code
        super().__init__(f"{code}: {message}")

def generate_pdf(html_file_path: str, api_key: str):
    url = 'https://api.syntaxdoc.com/pdf/generate'
    
    with open(html_file_path, 'rb') as f:
        files = [('files', ('index.html', f, 'text/html'))]
        data = {'options': json.dumps({'fileName': 'document'})}
        headers = {'X-API-Key': api_key}
        
        response = requests.post(url, headers=headers, files=files, data=data)
    
    if not response.ok:
        try:
            error = response.json()
            code = error.get('code', 'UNKNOWN_ERROR')
            message = error.get('message', 'Unknown error')
        except:
            code = 'UNKNOWN_ERROR'
            message = response.text
        
        raise SyntaxDocError(code, message, response.status_code)
    
    # Check if JSON response (hosted PDF) or binary
    if 'application/json' in response.headers.get('content-type', ''):
        return response.json()
    else:
        return response.content

# Usage
try:
    result = generate_pdf('document.html', 'YOUR_API_KEY')
    if isinstance(result, dict):
        print(f"Hosted PDF URL: {result['url']}")
    else:
        with open('output.pdf', 'wb') as f:
            f.write(result)
except SyntaxDocError as e:
    if e.status_code == 429:
        print(f"Quota exceeded: {e.message}")
    elif e.status_code == 401:
        print("Invalid API key")
    else:
        print(f"Error: {e.message}")

Retry Strategy

For transient errors (500), implement exponential backoff:

async function generatePdfWithRetry(htmlFile, maxRetries = 3) {
  const formData = new FormData();
  formData.append('files', htmlFile);

  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      const response = await fetch('https://api.syntaxdoc.com/pdf/generate', {
        method: 'POST',
        headers: { 'X-API-Key': 'YOUR_API_KEY' },
        body: formData
      });

      if (response.ok) {
        return await response.blob();
      }

      // Only retry on server errors
      if (response.status >= 500) {
        const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s
        console.log(`Retrying in ${delay}ms...`);
        await new Promise(resolve => setTimeout(resolve, delay));
        continue;
      }

      // Non-retryable error
      const error = await response.json();
      throw new Error(error.message);
    } catch (error) {
      if (attempt === maxRetries - 1) throw error;
    }
  }

  throw new Error('Max retries exceeded');
}
import time
import requests

def generate_pdf_with_retry(html_file_path: str, api_key: str, max_retries: int = 3):
    url = 'https://api.syntaxdoc.com/pdf/generate'
    
    for attempt in range(max_retries):
        with open(html_file_path, 'rb') as f:
            files = [('files', ('index.html', f, 'text/html'))]
            headers = {'X-API-Key': api_key}
            
            response = requests.post(url, headers=headers, files=files)
        
        if response.ok:
            return response.content
        
        # Only retry on server errors
        if response.status_code >= 500:
            delay = (2 ** attempt)  # 1s, 2s, 4s
            print(f"Retrying in {delay}s...")
            time.sleep(delay)
            continue
        
        # Non-retryable error
        error = response.json()
        raise Exception(error.get('message', 'Unknown error'))
    
    raise Exception('Max retries exceeded')

Debugging Tips

1. Check Request Format

Make sure you're using form-data (not JSON body):

# Correct ✅
curl -X POST https://api.syntaxdoc.com/pdf/generate \
  -H "X-API-Key: YOUR_KEY" \
  -F "files=@index.html" \
  -F 'options={"fileName":"test"}'

# Incorrect ❌
curl -X POST https://api.syntaxdoc.com/pdf/generate \
  -H "X-API-Key: YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"html": "<h1>Test</h1>"}'

2. Verify API Key

Test your API key:

curl https://api.syntaxdoc.com/api/keys \
  -H "Authorization: Bearer YOUR_AUTH_TOKEN"

3. Check Quota

Check your remaining conversions:

curl https://api.syntaxdoc.com/api/billing/subscription \
  -H "Authorization: Bearer YOUR_AUTH_TOKEN"

4. Validate HTML

Ensure your HTML is valid before sending:

  • All tags are properly closed
  • External resources are accessible
  • No JavaScript runtime errors

Need help? Check your usage dashboard or contact support with your request ID for faster debugging.

On this page