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
Code Status Description 200 OK Request succeeded 400 Bad Request Invalid request parameters 401 Unauthorized Missing or invalid authentication 403 Forbidden Valid auth but insufficient permissions 404 Not Found Resource doesn’t exist 429 Too Many Requests Rate limit exceeded 500 Internal Server Error Server-side error
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"
}
}
Machine-readable error code for programmatic handling
Human-readable error description
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"
}
Short status label (e.g. Unauthorized, Too Many Requests).
Human-readable description of the failure.
429 responses additionally include limit ("burst" or "daily") and retryAfter (seconds).
Error Codes Reference
Authentication & Authorization
Code HTTP Status Description UNAUTHORIZED401 Authentication required INVALID_TOKEN401 Invalid or expired API token INSUFFICIENT_PLAN403 Growth plan or higher required FORBIDDEN403 Access denied
Not Found
Code HTTP Status Description BRAND_NOT_FOUND404 Brand not found or not accessible COMPETITOR_NOT_FOUND404 Competitor not found SOURCE_NOT_FOUND404 Source not found PROMPT_NOT_FOUND404 Prompt not found ANSWER_NOT_FOUND404 Answer not found RESOURCE_NOT_FOUND404 Generic resource not found ENDPOINT_NOT_FOUND404 API endpoint does not exist
Bad Request
Code HTTP Status Description INVALID_REQUEST400 Invalid request format INVALID_PARAMETER400 Invalid parameter value MISSING_PARAMETER400 Missing required parameter INVALID_DATE_RANGE400 Invalid date range INVALID_SORT_FIELD400 Invalid sort field
Rate Limiting
Code HTTP Status Description RATE_LIMIT_EXCEEDED429 Rate limit exceeded
Server Errors
Code HTTP Status Description INTERNAL_ERROR500 Internal 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