Skip to main content
Evolution API uses standard HTTP status codes and consistent error response formats to communicate issues. Understanding these error patterns will help you build robust integrations.

HTTP Status Codes

Evolution API implements the following HTTP status codes (src/api/routes/index.router.ts:28-36):
Status CodeNameDescription
200OKRequest succeeded
201CREATEDResource created successfully
400BAD_REQUESTInvalid request parameters or body
401UNAUTHORIZEDMissing or invalid authentication
403FORBIDDENValid authentication but insufficient permissions
404NOT_FOUNDRequested resource does not exist
500INTERNAL_SERVER_ERRORServer encountered an unexpected error
All error responses include a consistent JSON structure for easy parsing and error handling.

Error Response Format

Evolution API error responses follow this standard format:
{
  "status": 400,
  "error": "Bad Request",
  "message": "Descriptive error message or array of errors"
}
status
number
required
The HTTP status code (400, 401, 403, 404, or 500)
error
string
required
A human-readable error name matching the HTTP status
message
string | array
required
Detailed error description. Can be a string or array of error messages

Error Types and Exceptions

Evolution API defines custom exception classes in src/exceptions/:

400 Bad Request

Implementation: src/exceptions/400.exception.ts Thrown when the request contains invalid parameters, missing required fields, or fails validation.
{
  "status": 400,
  "error": "Bad Request",
  "message": [
    "number is required",
    "text must be a string"
  ]
}
Common Causes:
  • Missing required fields in request body
  • Invalid data types (string instead of number)
  • JSONSchema validation failures
  • Malformed JSON in request body
  • Invalid phone number formats
  • File upload errors
Example Scenarios:
Request:
POST /message/sendText/my-instance
apikey: valid-token

{
  "text": "Hello"
  # Missing "number" field
}
Response (400):
{
  "status": 400,
  "error": "Bad Request",
  "message": ["requires property \"number\""]
}
Request:
POST /message/sendText/my-instance
apikey: valid-token

{
  "number": 5511999999999,  // Should be string
  "text": "Hello"
}
Response (400):
{
  "status": 400,
  "error": "Bad Request",
  "message": ["number is not of a type(s) string"]
}
Request:
POST /group/updateGroupPicture/my-instance?groupJid=invalid-format
apikey: valid-token

{ "image": "https://..." }
Response (400):
{
  "status": 400,
  "error": "Bad Request",
  "message": "The group id needs to be informed in the query"
}
The validation logic is in src/api/abstract/abstract.router.ts:96-144.

401 Unauthorized

Implementation: src/exceptions/401.exception.ts Thrown when authentication is missing or invalid.
{
  "status": 401,
  "error": "Unauthorized",
  "message": "Unauthorized"
}
Common Causes:
  • Missing apikey header
  • Invalid global API key
  • Invalid instance token
  • Using instance token for another instance
  • Expired or revoked credentials
Example Scenarios:
Request:
GET /instance/connectionState/my-instance
# Missing apikey header
Response (401):
{
  "status": 401,
  "error": "Unauthorized",
  "message": "Unauthorized"
}
Validated in src/api/guards/auth.guard.ts:15-17.
Request:
GET /instance/connectionState/my-instance
apikey: wrong-key-12345
Response (401):
{
  "status": 401,
  "error": "Unauthorized",
  "message": "Unauthorized"
}
The key is validated against both the global key and instance tokens in src/api/guards/auth.guard.ts:19-50.
Request:
POST /message/sendText/instance-a
apikey: token-for-instance-b

{ "number": "123", "text": "test" }
Response (401):
{
  "status": 401,
  "error": "Unauthorized",
  "message": "Unauthorized"
}

403 Forbidden

Implementation: src/exceptions/403.exception.ts Thrown when authentication is valid but the operation is not permitted.
{
  "status": 403,
  "error": "Forbidden",
  "message": [
    "Missing global api key",
    "The global api key must be set"
  ]
}
Common Causes:
  • Using instance token to create instances (requires global key)
  • Attempting to access protected administrative endpoints
  • IP address not in metrics allowlist
  • Invalid metrics authentication
Example Scenarios:
Request:
POST /instance/create
apikey: some-instance-token

{
  "instanceName": "new-instance",
  "token": "new-token"
}
Response (403):
{
  "status": 403,
  "error": "Forbidden",
  "message": [
    "Missing global api key",
    "The global api key must be set"
  ]
}
Instance creation requires the global API key (src/api/guards/auth.guard.ts:23-25).
Request:
GET /metrics
# From IP 203.0.113.5 (not in allowlist)
Response (403):
Forbidden: IP not allowed
IP validation is in src/api/routes/index.router.ts:48-63.
Request:
GET /assets/../../../etc/passwd
Response (403):
Forbidden
Path traversal protection is in src/api/routes/index.router.ts:169-183.

404 Not Found

Implementation: src/exceptions/404.exception.ts Thrown when the requested resource does not exist.
{
  "status": 404,
  "error": "Not Found",
  "message": "Instance my-instance not found"
}
Common Causes:
  • Instance name does not exist
  • Chat or message ID not found
  • Group JID does not exist
  • Contact not in WhatsApp database
  • File or media not found
Example Scenarios:
Request:
GET /instance/connectionState/nonexistent-instance
apikey: valid-global-key
Response (404):
{
  "status": 404,
  "error": "Not Found",
  "message": "Instance nonexistent-instance not found"
}
Request:
GET /chat/findMessages/[email protected]
apikey: valid-token
Response (404):
{
  "status": 404,
  "error": "Not Found",
  "message": "Chat not found"
}
Request:
GET /assets/nonexistent-file.png
Response (404):
File not found
File existence check is in src/api/routes/index.router.ts:185-190.

500 Internal Server Error

Implementation: src/exceptions/500.exception.ts Thrown when the server encounters an unexpected error.
{
  "status": 500,
  "error": "Internal Server Error",
  "message": "An unexpected error occurred while processing your request"
}
Common Causes:
  • Database connection failures
  • WhatsApp connection errors
  • Unhandled exceptions in business logic
  • External service timeouts
  • File system errors
  • Memory or resource exhaustion
Example Scenarios:
Response (500):
{
  "status": 500,
  "error": "Internal Server Error",
  "message": "Database connection failed"
}
Database errors are logged and caught by the error handling middleware.
Response (500):
{
  "status": 500,
  "error": "Internal Server Error",
  "message": "WhatsApp connection timeout"
}
Request:
GET /metrics
Authorization: Basic dXNlcjpwYXNz
Response (500):
Metrics authentication not configured
Returned when metrics auth is required but not configured (src/api/routes/index.router.ts:71-73).

Validation Errors

Evolution API uses JSONSchema7 for request validation (src/api/abstract/abstract.router.ts:46-60). Validation errors return detailed information:

Single Validation Error

{
  "status": 400,
  "error": "Bad Request",
  "message": ["number is required"]
}

Multiple Validation Errors

{
  "status": 400,
  "error": "Bad Request",
  "message": [
    "number is required",
    "text must be a string",
    "delay must be a number"
  ]
}

Schema Description Override

If the JSONSchema includes a description field, it’s used as the error message:
const schema = {
  type: 'object',
  properties: {
    number: {
      type: 'string',
      description: 'Phone number in international format (e.g., 5511999999999)'
    }
  }
};
Error Response:
{
  "status": 400,
  "error": "Bad Request",
  "message": ["Phone number in international format (e.g., 5511999999999)"]
}

Error Handling Best Practices

Client-Side Error Handling

Implement comprehensive error handling in your client code:
async function sendMessage(instanceName, token, number, text) {
  try {
    const response = await fetch(
      `https://api.yourdomain.com/message/sendText/${instanceName}`,
      {
        method: 'POST',
        headers: {
          'apikey': token,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ number, text })
      }
    );
    
    if (!response.ok) {
      const error = await response.json();
      
      switch (error.status) {
        case 400:
          console.error('Validation error:', error.message);
          // Show user-friendly validation errors
          break;
        case 401:
          console.error('Authentication failed');
          // Redirect to login or refresh token
          break;
        case 404:
          console.error('Instance not found');
          // Prompt user to verify instance name
          break;
        case 500:
          console.error('Server error:', error.message);
          // Retry with exponential backoff
          break;
        default:
          console.error('Unexpected error:', error);
      }
      
      throw error;
    }
    
    return await response.json();
  } catch (error) {
    // Network errors (no response from server)
    if (!error.status) {
      console.error('Network error:', error.message);
      // Implement retry logic
    }
    throw error;
  }
}

Retry Logic for 500 Errors

Implement exponential backoff for transient server errors:
async function requestWithRetry(url, options, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      const response = await fetch(url, options);
      
      if (response.status === 500 && attempt < maxRetries - 1) {
        const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s
        console.log(`Retry attempt ${attempt + 1} after ${delay}ms`);
        await new Promise(resolve => setTimeout(resolve, delay));
        continue;
      }
      
      return response;
    } catch (error) {
      if (attempt === maxRetries - 1) throw error;
      
      const delay = Math.pow(2, attempt) * 1000;
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

Logging and Monitoring

Log errors for debugging and monitoring:
const logError = (context, error) => {
  const logEntry = {
    timestamp: new Date().toISOString(),
    context,
    status: error.status,
    errorType: error.error,
    message: error.message,
    requestId: error.requestId // If your implementation adds request IDs
  };
  
  console.error(JSON.stringify(logEntry));
  
  // Send to monitoring service
  // monitoringService.logError(logEntry);
};

Troubleshooting Common Errors

”Unauthorized” on Valid Requests

Symptoms:
  • Receiving 401 errors despite using correct API key
  • Authentication works in some environments but not others
Solutions:
  1. Check header name: Ensure you’re using apikey (lowercase)
    # Correct
    apikey: your-token
    
    # Wrong
    ApiKey: your-token
    API-Key: your-token
    
  2. Verify no extra whitespace: Trim API keys before sending
    const apiKey = process.env.API_KEY.trim();
    
  3. Check instance exists: Verify the instance name is correct
    GET /instance/fetchInstances
    apikey: global-key
    
  4. Database check: Ensure instance token matches database record
    SELECT name, token FROM Instance WHERE name = 'your-instance';
    

”Bad Request” with Unclear Messages

Symptoms:
  • Receiving 400 errors with generic validation messages
  • Not sure which field is causing the error
Solutions:
  1. Enable debug logging: Check server logs for detailed validation errors
    LOG_LEVEL=DEBUG,INFO,WARN,ERROR
    
  2. Test with minimal payload: Start with required fields only
    {
      "number": "5511999999999",
      "text": "test"
    }
    
  3. Validate JSON syntax: Use a JSON validator to ensure proper formatting
  4. Check data types: Ensure strings are quoted, numbers are not
    {
      "number": "5511999999999",  // String
      "delay": 1000               // Number (no quotes)
    }
    

”Instance not found” Immediately After Creation

Symptoms:
  • Instance creation succeeds (201 response)
  • Immediate subsequent requests return 404
Solutions:
  1. Check instance name: Ensure you’re using the exact name from creation
    const created = await createInstance({ instanceName: 'test' });
    // Use: created.instance.instanceName
    
  2. Wait for initialization: Add a small delay after creation
    await createInstance({ instanceName: 'test' });
    await new Promise(resolve => setTimeout(resolve, 1000));
    await connectToWhatsApp('test');
    
  3. Check database: Verify instance was persisted
    GET /instance/fetchInstances
    apikey: global-key
    

”Internal Server Error” on Message Send

Symptoms:
  • Message endpoint returns 500 error
  • Other endpoints work fine
Solutions:
  1. Check instance connection: Ensure instance is connected to WhatsApp
    GET /instance/connectionState/your-instance
    apikey: instance-token
    
  2. Verify phone number format: Use international format without ’+’
    // Correct
    { "number": "5511999999999" }
    
    // Wrong
    { "number": "+55 11 99999-9999" }
    
  3. Check server logs: Look for specific error messages
    docker logs evolution-api | grep ERROR
    
  4. Test WhatsApp connection: Try reconnecting the instance
    POST /instance/restart/your-instance
    apikey: instance-token
    

Error Logging

Evolution API logs errors using the Logger service (src/config/logger.config.ts):
logger.error(message);  // Logs validation and runtime errors
Configure log levels:
LOG_LEVEL=ERROR,WARN,DEBUG,INFO,LOG,VERBOSE
LOG_COLOR=true
LOG_BAILEYS=error  # WhatsApp library logs: fatal, error, warn, info, debug, trace
Error logs include timestamps, context, and stack traces for debugging. Monitor logs in production to identify patterns and fix issues proactively.

Next Steps

Authentication

Learn about API authentication and tokens

API Overview

Return to API overview

Instance Management

Create and manage instances

Send Messages

Start sending WhatsApp messages

Build docs developers (and LLMs) love