Skip to main content
The Qwairy API returns two error shapes depending on where the failure occurs:
  • Resource errors (validation and not-found errors raised inside an endpoint) return a nested object: { "error": { "code", "message" } }. Use the code for programmatic handling.
  • Authentication and rate-limit errors (raised by the API gateway before the endpoint runs — 401 and 429) return a flat object: { "error", "message" }. Here error is a short status label, not a machine-readable code.
Always branch on the HTTP status first, then read the body in the shape matching that status.

HTTP Status Codes

CodeStatusDescription
200OKRequest succeeded
400Bad RequestInvalid request parameters
401UnauthorizedMissing or invalid authentication
403ForbiddenValid auth but insufficient permissions
404Not FoundResource doesn’t exist
429Too Many RequestsRate limit exceeded
500Internal Server ErrorServer-side error

Error Response Format

Resource errors (nested)

Validation and not-found errors raised inside an endpoint return a structured error object:
{
  "error": {
    "code": "BRAND_NOT_FOUND",
    "message": "Brand not found or not accessible"
  }
}
error
object

Authentication & rate-limit errors (flat)

401 (authentication) and 429 (rate limit) responses are produced by the API gateway and use a flat shape — error is a short status label, not a code:
{
  "error": "Unauthorized",
  "message": "Missing or invalid Authorization header. Use: Authorization: Bearer qw-api-xxx"
}
error
string
Short status label (e.g. Unauthorized, Too Many Requests).
message
string
Human-readable description of the failure.
429 responses additionally include limit ("burst" or "daily") and retryAfter (seconds).

Error Codes Reference

Authentication & Authorization

CodeHTTP StatusDescription
UNAUTHORIZED401Authentication required
INVALID_TOKEN401Invalid or expired API token
INSUFFICIENT_PLAN403Growth plan or higher required
FORBIDDEN403Access denied

Not Found

CodeHTTP StatusDescription
BRAND_NOT_FOUND404Brand not found or not accessible
COMPETITOR_NOT_FOUND404Competitor not found
SOURCE_NOT_FOUND404Source not found
PROMPT_NOT_FOUND404Prompt not found
ANSWER_NOT_FOUND404Answer not found
RESOURCE_NOT_FOUND404Generic resource not found
ENDPOINT_NOT_FOUND404API endpoint does not exist

Bad Request

CodeHTTP StatusDescription
INVALID_REQUEST400Invalid request format
INVALID_PARAMETER400Invalid parameter value
MISSING_PARAMETER400Missing required parameter
INVALID_DATE_RANGE400Invalid date range
INVALID_SORT_FIELD400Invalid sort field

Rate Limiting

CodeHTTP StatusDescription
RATE_LIMIT_EXCEEDED429Rate limit exceeded

Server Errors

CodeHTTP StatusDescription
INTERNAL_ERROR500Internal server error

Common Errors

Authentication Errors (401)

Authentication failures use the flat shape:
// Missing or malformed Authorization header
{
  "error": "Unauthorized",
  "message": "Missing or invalid Authorization header. Use: Authorization: Bearer qw-api-xxx"
}

// Invalid token, revoked token, or plan below Growth
{
  "error": "Unauthorized",
  "message": "Invalid API token or insufficient subscription plan (Growth+ required)"
}
Solution: Ensure you’re using a valid token with the correct Bearer prefix, on a Growth plan or higher.

Permission Errors (403)

// Team subscription doesn't include API access
{
  "error": {
    "code": "INSUFFICIENT_PLAN",
    "message": "Growth plan or higher required"
  }
}
Solution: Verify you have the correct permissions and plan level.

Not Found Errors (404)

// Brand doesn't exist
{
  "error": {
    "code": "BRAND_NOT_FOUND",
    "message": "Brand not found or not accessible"
  }
}

// Competitor doesn't exist
{
  "error": {
    "code": "COMPETITOR_NOT_FOUND",
    "message": "Competitor not found"
  }
}
Solution: Check that the resource ID is correct and belongs to your team.

Rate Limit Errors (429)

Rate-limit responses use the flat shape and add limit and retryAfter:
{
  "error": "Too Many Requests",
  "limit": "burst",
  "message": "Per-minute rate limit exceeded. Please try again in 45 seconds.",
  "retryAfter": 45
}
limit is "burst" (per-minute) or "daily". Solution: Wait retryAfter seconds (or until the X-RateLimit-Reset / X-RateLimit-Daily-Reset timestamp), then retry. See Rate Limits.

Server Errors (500)

{
  "error": {
    "code": "INTERNAL_ERROR",
    "message": "An unexpected error occurred"
  }
}
Solution: Retry after a few seconds. If the error persists, contact support.

Handling Errors

JavaScript/TypeScript Example

async function callQwairyAPI(endpoint: string) {
  const response = await fetch(`https://www.qwairy.co/api/v1${endpoint}`, {
    headers: {
      'Authorization': `Bearer ${process.env.QWAIRY_API_TOKEN}`,
    },
  });

  if (!response.ok) {
    const data = await response.json().catch(() => ({}));

    // Auth and rate-limit errors use the flat shape: { error, message }
    if (response.status === 401) {
      throw new Error(`Authentication failed: ${data.message ?? data.error}`);
    }
    if (response.status === 429) {
      const retryAfter = data.retryAfter ?? response.headers.get('Retry-After');
      throw new Error(`Rate limited. Retry after: ${retryAfter}s`);
    }

    // Resource errors use the nested shape: { error: { code, message } }
    const code = data.error?.code ?? 'UNKNOWN';
    const message = data.error?.message ?? data.message ?? `HTTP ${response.status}`;

    switch (code) {
      case 'INSUFFICIENT_PLAN':
      case 'FORBIDDEN':
        throw new Error(`Permission denied: ${message}`);
      case 'BRAND_NOT_FOUND':
      case 'COMPETITOR_NOT_FOUND':
      case 'SOURCE_NOT_FOUND':
      case 'PROMPT_NOT_FOUND':
      case 'ANSWER_NOT_FOUND':
        throw new Error(`Resource not found: ${message}`);
      default:
        throw new Error(`API error [${code}]: ${message}`);
    }
  }

  return response.json();
}

Python Example

import requests
import os

def call_qwairy_api(endpoint):
    response = requests.get(
        f"https://www.qwairy.co/api/v1{endpoint}",
        headers={"Authorization": f"Bearer {os.environ['QWAIRY_API_TOKEN']}"}
    )

    if not response.ok:
        body = response.json() if response.content else {}

        # Auth and rate-limit errors use the flat shape: { error, message }
        if response.status_code == 401:
            raise Exception(f"Authentication failed: {body.get('message', body.get('error'))}")
        if response.status_code == 429:
            retry_after = body.get('retryAfter', response.headers.get('Retry-After'))
            raise Exception(f"Rate limited. Retry after: {retry_after}s")

        # Resource errors use the nested shape: { error: { code, message } }
        error = body.get('error', {}) if isinstance(body.get('error'), dict) else {}
        code = error.get('code', 'UNKNOWN')
        message = error.get('message', body.get('message', 'Unknown error'))

        if code in ['INSUFFICIENT_PLAN', 'FORBIDDEN']:
            raise Exception(f"Permission denied: {message}")
        elif code.endswith('_NOT_FOUND'):
            raise Exception(f"Not found: {message}")
        else:
            raise Exception(f"API error [{code}]: {message}")

    return response.json()

Need Help?

If you encounter persistent errors or unexpected behavior, contact us at team@qwairy.co with:
  • The endpoint you’re calling
  • The full error response
  • Your request headers (without the token)
  • The timestamp of the request