Skip to main content

Error Response Format

All API errors follow a consistent JSON structure:
{
  "error": {
    "code": "invalid_request",
    "message": "The url field is required",
    "field": "url"
  }
}
Fields:
  • code - Machine-readable error code (e.g., invalid_request, rate_limit_exceeded)
  • message - Human-readable error description
  • field - (Optional) The specific field that caused the error

HTTP Status Codes

Status CodeDescription
200 OKRequest successful
201 CreatedResource created successfully
400 Bad RequestInvalid request parameters
401 UnauthorizedMissing or invalid API key
403 ForbiddenInsufficient permissions or quota exceeded
404 Not FoundResource not found
409 ConflictResource conflict (e.g., slug already exists)
429 Too Many RequestsRate limit exceeded
500 Internal Server ErrorServer error (contact support if persists)

Common Errors

401 Unauthorized - Missing API Key

{
  "error": {
    "code": "missing_api_key",
    "message": "API key is required. Include it in X-Api-Key header."
  }
}
Solution: Add your API key to the X-Api-Key header.

401 Unauthorized - Invalid API Key

{
  "error": {
    "code": "invalid_api_key",
    "message": "API key is invalid or has been revoked"
  }
}
Solutions:
  • Verify you’ve copied the full API key correctly
  • Check if the key has been revoked in the API Dashboard
  • Generate a new API key if needed

403 Forbidden - Plan Upgrade Required

{
  "error": {
    "code": "plan_upgrade_required",
    "message": "Your subscription plan does not include API access. Please upgrade to Growth or higher.",
    "currentPlan": "solo"
  }
}
Solution: Upgrade to a Growth plan ($35/month) or higher.

403 Forbidden - Quota Exceeded

{
  "error": {
    "code": "quota_exceeded",
    "message": "You've reached your plan's link limit",
    "currentPlan": "growth",
    "currentLinks": 1000,
    "maxLinks": 1000
  }
}
Solutions:

404 Not Found

{
  "error": {
    "code": "link_not_found",
    "message": "Link not found"
  }
}
Solutions:
  • Check that the link ID is correct
  • Verify the link hasn’t been deleted
  • Ensure you have permission to access this link

409 Conflict - Slug Already Exists

{
  "error": {
    "code": "slug_already_exists",
    "message": "The slug 'summer' is already in use"
  }
}
Solutions:
  • Choose a different slug
  • Omit the slug field to auto-generate a random one
  • Check if you already created this link

429 Too Many Requests - Rate Limit Exceeded

{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Rate limit exceeded",
    "limit": 1000,
    "remaining": 0,
    "resetAt": 1738886400
  }
}
Solution: Wait until the rate limit resets (see resetAt timestamp) or upgrade your plan.

Rate Limiting

Rate Limit Headers

Every API response includes rate limit information in the headers:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1738886400
  • X-RateLimit-Limit - Your maximum requests per hour
  • X-RateLimit-Remaining - Requests remaining in current window
  • X-RateLimit-Reset - Unix timestamp when limit resets

Rate Limits by Plan

PlanRequests/HourRequests/Day
Growth1,00010,000
Scaling5,00050,000
Dominance10,000100,000

Handling Rate Limits

async function makeRequest(url, options) {
  const response = await fetch(url, options);

  // Check if rate limited
  if (response.status === 429) {
    const resetTime = response.headers.get('X-RateLimit-Reset');
    const waitMs = (resetTime * 1000) - Date.now();

    console.log(`Rate limited. Waiting ${waitMs}ms...`);
    await new Promise(resolve => setTimeout(resolve, waitMs));

    // Retry request
    return makeRequest(url, options);
  }

  return response.json();
}

Best Practices for Rate Limiting

Check X-RateLimit-Remaining before making requests:
const remaining = response.headers.get('X-RateLimit-Remaining');
if (remaining < 10) {
  console.warn('Approaching rate limit!');
}
Retry failed requests with increasing delays:
async function retryWithBackoff(fn, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (error) {
      if (i === maxRetries - 1) throw error;
      const delay = Math.pow(2, i) * 1000; // 1s, 2s, 4s
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}
Use bulk endpoints to reduce request count:
  • Use POST /v1/links/bulk instead of multiple single creates
  • Use DELETE /v1/links/bulk for deleting multiple links
Cache data that doesn’t change frequently:
const cache = new Map();

async function getCachedLink(linkId) {
  if (cache.has(linkId)) {
    return cache.get(linkId);
  }

  const link = await fetchLink(linkId);
  cache.set(linkId, link);
  return link;
}

Retry Logic

Implement automatic retries for transient errors (500, 502, 503, 504):
async function fetchWithRetry(url, options, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch(url, options);

      // Don't retry client errors (4xx)
      if (response.status >= 400 && response.status < 500) {
        throw new Error(`Client error: ${response.status}`);
      }

      // Retry server errors (5xx)
      if (response.status >= 500 && attempt < maxRetries) {
        const delay = Math.pow(2, attempt) * 1000;
        console.log(`Retry ${attempt}/${maxRetries} after ${delay}ms`);
        await new Promise(resolve => setTimeout(resolve, delay));
        continue;
      }

      return response.json();
    } catch (error) {
      if (attempt === maxRetries) throw error;
    }
  }
}

Validation Errors

Field-specific validation errors include the field property:
{
  "error": {
    "code": "invalid_url",
    "message": "The URL must be a valid HTTP or HTTPS URL",
    "field": "url"
  }
}
Common validation errors:
Error CodeDescription
missing_required_fieldRequired field is missing
invalid_urlURL is not a valid HTTP/HTTPS URL
invalid_slugSlug contains invalid characters
invalid_emailEmail address is invalid
value_too_longField value exceeds maximum length
value_too_shortField value is below minimum length

Error Logging

Log errors for debugging:
try {
  const response = await fetch('https://api.bouncy.ai/v1/links', {
    method: 'POST',
    headers: {
      'X-Api-Key': 'bcy_live_pk_YOUR_API_KEY',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ url: 'https://example.com' })
  });

  if (!response.ok) {
    const error = await response.json();
    console.error('API Error:', {
      status: response.status,
      code: error.error?.code,
      message: error.error?.message,
      timestamp: new Date().toISOString()
    });
    throw new Error(error.error?.message || 'API request failed');
  }

  return response.json();
} catch (error) {
  // Log to error tracking service (Sentry, etc.)
  console.error('Request failed:', error);
  throw error;
}

Need Help?

If you encounter persistent errors: