Skip to content

API Error Responses

This guide provides comprehensive information about error responses from the Grizzly API, including HTTP status codes, error formats, and common error scenarios.

Error Response Format

All API error responses follow a consistent JSON format:

{
  "message": "Description of what went wrong"
}

Key Points:

  • Errors always include a message field describing the error
  • The HTTP status code indicates the error category
  • Error messages are designed to be actionable and developer-friendly

HTTP Status Codes

The Grizzly API uses standard HTTP status codes to indicate success or failure.

2xx Success

Code Status Description
200 OK Request succeeded
201 Created Resource successfully created

4xx Client Errors

Code Status Description Common Causes
400 Bad Request Invalid request format or parameters Missing required fields, invalid JSON, malformed data
401 Unauthorized Authentication failed Invalid or missing API key, expired token
403 Forbidden Insufficient permissions or quota exceeded Missing required permission flags, resource limit reached
404 Not Found Resource doesn't exist KeyRing not found, User doesn't exist, invalid ID
422 Unprocessable Entity Request is valid but contains semantic errors Asset validation failed

5xx Server Errors

Code Status Description
500 Internal Server Error Unexpected server error

Common Error Scenarios

Authentication Errors (401)

Invalid API Key

Request:

curl -X GET https://your-tenant.grizzlycbg.io/ks/keyrings \
  -H "Authorization: Bearer invalid-api-key"

Response:

HTTP/1.1 401 Unauthorized
Content-Type: application/json

{
  "message": "Unauthorized"
}

Common Causes:

  • API key is incorrect or doesn't exist
  • API key has been disabled
  • API key belongs to a different tenant
  • Missing Bearer prefix in Authorization header

Solutions:

  • Verify API key is correct and active
  • Check that API key is properly formatted: Bearer apikey-xxx
  • Ensure API key belongs to the correct tenant
Missing Authorization Header

Request:

curl -X GET https://your-tenant.grizzlycbg.io/ks/keyrings

Response:

HTTP/1.1 401 Unauthorized
Content-Type: application/json

{
  "message": "Unauthorized"
}

Solution: Always include the Authorization header with Bearer token:

curl -X GET https://your-tenant.grizzlycbg.io/ks/keyrings \
  -H "Authorization: Bearer your-api-key"


Permission Errors (403)

Insufficient Permissions

Request:

curl -X POST https://your-tenant.grizzlycbg.io/ks/rotate \
  -H "Authorization: Bearer apikey-xxx" \
  -H "Content-Type: application/json" \
  -d '{"ringname": "Finance"}'

Response:

HTTP/1.1 403 Forbidden
Content-Type: application/json

{
  "message": "Insufficient permissions to rotate keys for KeyRing 'Finance'"
}

Explanation: The API key doesn't have the required permission flag for this operation.

Required Permission: - keyring.Finance.rotate or keyring.*.rotate

Solution: Update the API key to include the required permission flag.


Bad Request Errors (400)

Missing Required Field

Request:

curl -X POST https://your-tenant.grizzlycbg.io/ks/keyrings \
  -H "Authorization: Bearer apikey-xxx" \
  -H "Content-Type: application/json" \
  -d '{}'

Response:

HTTP/1.1 400 Bad Request
Content-Type: application/json

{
  "message": "Missing required field: name"
}

Explanation: The name field is required when creating a KeyRing.

Correct Request:

curl -X POST https://your-tenant.grizzlycbg.io/ks/keyrings \
  -H "Authorization: Bearer apikey-xxx" \
  -H "Content-Type: application/json" \
  -d '{"name": "Finance"}'

Invalid JSON

Request:

curl -X POST https://your-tenant.grizzlycbg.io/ks/keyrings \
  -H "Authorization: Bearer apikey-xxx" \
  -H "Content-Type: application/json" \
  -d '{"name": "Finance"'

Response:

HTTP/1.1 400 Bad Request
Content-Type: application/json

{
  "message": "Invalid JSON in request body"
}

Explanation: The JSON payload is malformed (missing closing brace).

Solution: Ensure JSON is properly formatted and valid.

Missing Content-Type Header

Request:

curl -X POST https://your-tenant.grizzlycbg.io/ks/keyrings \
  -H "Authorization: Bearer apikey-xxx" \
  -d '{"name": "Finance"}'

Response:

HTTP/1.1 400 Bad Request
Content-Type: application/json

{
  "message": "Content-Type must be application/json"
}

Solution: Always include Content-Type: application/json header for POST/PUT requests.

Password Validation Failed

Request:

curl -X POST https://your-tenant.grizzlycbg.io/auth/user/invite-complete \
  -H "Content-Type: application/json" \
  -d '{
    "token": "invite-token-123",
    "password": "weak"
  }'

Response:

HTTP/1.1 400 Bad Request
Content-Type: application/json

{
  "state": "malformed-password",
  "results": {
    "isValid": false,
    "details": {
      "minLength": 8,
      "newPasswordLength": 4,
      "minUppercase": 1,
      "newPasswordUppercase": 0,
      "minLowercase": 1,
      "newPasswordLowercase": 4,
      "minSpecialChar": 1,
      "newPasswordSpecialChar": 0,
      "minNumber": 1,
      "newPasswordNumber": 0
    }
  }
}

Explanation: Password doesn't meet complexity requirements.

Password Requirements:

  • Minimum length: 8 characters
  • At least 1 uppercase letter
  • At least 1 lowercase letter
  • At least 1 number
  • At least 1 special character

Not Found Errors (404)

KeyRing Not Found

Request:

curl -X GET https://your-tenant.grizzlycbg.io/ks/keyrings/NonExistentRing \
  -H "Authorization: Bearer apikey-xxx"

Response:

HTTP/1.1 404 Not Found
Content-Type: application/json

{
  "message": "KeyRing 'NonExistentRing' not found"
}

Common Causes:

  • KeyRing name is misspelled
  • KeyRing doesn't exist
  • Using wrong case (though names are case-insensitive)

Solution:

  • List all KeyRings: GET /ks/keyrings
  • Verify KeyRing name spelling
  • Create the KeyRing if it doesn't exist
User Not Found

Request:

curl -X GET https://your-tenant.grizzlycbg.io/auth/users/nonexistent \
  -H "Authorization: Bearer apikey-xxx"

Response:

HTTP/1.1 404 Not Found
Content-Type: application/json

{
  "message": "User 'nonexistent' not found"
}

Solution:

  • Verify username is correct
  • Check if using email instead of username (use ?email=true query parameter)
  • List all users: GET /auth/users
Account Not Found

Request:

curl -X GET https://your-tenant.grizzlycbg.io/auth/accounts/invalid-id \
  -H "Authorization: Bearer apikey-xxx"

Response:

HTTP/1.1 404 Not Found
Content-Type: application/json

{
  "message": "Account not found"
}

Solution:

  • Verify Account ID is correct
  • Use Account UID if applicable: GET /auth/accounts/{id}?uid=true

Validation Errors (422)

Asset Validation Failed

Request:

curl -X POST https://your-tenant.grizzlycbg.io/ks/decrypt \
  -H "Authorization: Bearer apikey-xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "hash": "invalid-hash",
    "header": "tampered-header"
  }'

Response:

HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json

{
  "ringId": "ring-123",
  "ringname": "finance",
  "key": {
    "type": "aes256",
    "id": "key-456"
  },
  "asset": {
    "valid": false,
    "id": "asset-789",
    "message": "Asset signature verification failed",
    "data": {}
  }
}

Explanation: The encrypted data's asset metadata has been tampered with or is corrupted. The cryptographic signature doesn't match.

Common Causes:

  • Data was modified after encryption
  • Header was separated from payload
  • Encoding issues during storage/transmission

Solution:

  • Ensure encrypted data is stored and retrieved without modification
  • Verify no middleware is altering the data
  • Check for encoding/decoding issues (base64, etc.)

Server Errors (500)

Internal Server Error

Request:

curl -X POST https://your-tenant.grizzlycbg.io/ks/rotate \
  -H "Authorization: Bearer apikey-xxx" \
  -H "Content-Type: application/json" \
  -d '{"ringname": "Finance"}'

Response:

HTTP/1.1 500 Internal Server Error
Content-Type: application/json

{
  "message": "An unexpected error occurred while rotating the key"
}

Explanation: An unexpected server-side error occurred.

What to Do:

  1. Retry the request after a brief delay
  2. Check if the issue persists
  3. Review request parameters for potential issues
  4. Contact support if the error continues
  5. Provide the request details and timestamp for investigation

Resource-Specific Errors

KeyRings

Duplicate KeyRing Name (400)

{
  "message": "The KeyRing 'Finance' already exists. KeyRings must have unique names."
}

KeyRing Deletion Failed (400)

{
  "message": "Cannot delete KeyRing. Active API keys are still using this KeyRing."
}

API Keys

API Key Not Found (404)

{
  "message": "API Key not found"
}

Account Not Found When Creating API Key (404)

{
  "message": "Account not found. Cannot create API key."
}

Users

Duplicate Username (400)

{
  "message": "User with username 'john.doe' already exists"
}

Duplicate Email (400)

{
  "message": "User with email 'john@example.com' already exists",
  "errors": {
    "email": "Email already registered"
  }
}

Invitations

Invalid Invite Token (401)

{
  "state": "invalid"
}

Expired Invite Token (401)

{
  "state": "expired"
}

Login

Invalid Credentials (400)

{
  "message": "Invalid username or password"
}

Error Handling Best Practices

1. Always Check Status Codes

const response = await fetch('https://your-tenant.grizzlycbg.io/ks/keyrings', {
   headers: {
      'Authorization': `Bearer ${apiKey}`
   }
})

if (!response.ok) {
   const error = await response.json()
   console.error(`API Error (${response.status}):`, error.message)

   // Handle specific error codes
   if (response.status === 401) {
      // Refresh authentication
   } else if (response.status === 403) {
      // Check permissions
   } else if (response.status === 404) {
      // Resource doesn't exist
   }
}

2. Implement Retry Logic

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

         if (response.ok) {
            return await response.json()
         }

         // Don't retry client errors (4xx)
         if (response.status >= 400 && response.status < 500) {
            const error = await response.json()
            throw new Error(error.message)
         }

         // Retry server errors (5xx)
         if (response.status >= 500 && attempt < maxRetries) {
            const delay = Math.pow(2, attempt) * 1000 // Exponential backoff
            await new Promise(resolve => setTimeout(resolve, delay))
            continue
         }

         const error = await response.json()
         throw new Error(error.message)

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

3. Parse Error Messages

async function createKeyRing(name) {
   try {
      const response = await fetch('https://your-tenant.grizzlycbg.io/ks/keyrings', {
         method: 'POST',
         headers: {
            'Authorization': `Bearer ${apiKey}`,
            'Content-Type': 'application/json'
         },
         body: JSON.stringify({ name })
      })

      if (!response.ok) {
         const error = await response.json()

         // Handle specific error scenarios
         if (error.message.includes('already exists')) {
            console.log('KeyRing already exists, fetching existing one...')
            return await getKeyRing(name)
         }

         if (error.message.includes('maximum number')) {
            console.error('Plan limit reached:', error.message)
            // Prompt user to upgrade plan
            return null
         }

         throw new Error(error.message)
      }

      return await response.json()

   } catch (error) {
      console.error('Failed to create KeyRing:', error.message)
      throw error
   }
}

4. Log Errors for Debugging

async function makeApiCall(url, options) {
   const requestId = crypto.randomUUID()

   console.log(`[${requestId}] Request:`, {
      url,
      method: options.method,
      timestamp: new Date().toISOString()
   })

   try {
      const response = await fetch(url, options)

      if (!response.ok) {
         const error = await response.json()

         console.error(`[${requestId}] Error Response:`, {
            status: response.status,
            message: error.message,
            timestamp: new Date().toISOString()
         })

         throw new Error(error.message)
      }

      console.log(`[${requestId}] Success:`, {
         status: response.status,
         timestamp: new Date().toISOString()
      })

      return await response.json()

   } catch (error) {
      console.error(`[${requestId}] Request Failed:`, error.message)
      throw error
   }
}

Next Steps