Error Handling
The CannMenus API uses standard HTTP status codes and returns detailed error messages to help you diagnose issues quickly.
HTTP Status Codes
| Code | Meaning | Description |
|---|---|---|
200 | Success | Request completed successfully |
403 | Forbidden | No API token was sent |
404 | Not Found | Invalid or expired API token, or the endpoint/resource doesn't exist |
422 | Unprocessable Entity | Validation error — missing or invalid parameters |
429 | Too Many Requests | Your organization exceeded its rate limit. Wait the number of seconds in the Retry-After header, then retry |
500 | Server Error | Something went wrong on our end |
Error Response Format
REST API (/v1, /v2) errors return a JSON object with a detail field. For most errors, detail is a string. For validation errors (422), detail is an array of objects:
{
"detail": "Invalid or expired token"
}
| Field | Description |
|---|---|
detail | Human-readable description of the problem (string), or an array of validation error objects for 422 responses |
Common Errors
Missing or Invalid Parameters
Status code: 422
{
"detail": [
{
"loc": ["query", "states"],
"msg": "field required",
"type": "value_error.missing"
}
]
}
Fix: Check that all required parameters are included and properly formatted. FastAPI returns validation errors as an array of objects with loc, msg, and type fields.
Missing API Token
Status code: 403
{
"detail": "Missing API token. Send it as 'X-Token: <token>' or 'Authorization: Bearer <token>'."
}
Fix: Send your token in the X-Token header. See Authentication.
Invalid API Token
Status code: 404
{
"detail": "Invalid or expired token"
}
Fix: Verify your token in the X-Token header. Generate a new token from the dashboard if needed.
Rate Limit Exceeded
Status code: 429, with a Retry-After header
{
"detail": "Rate limit exceeded: <limit> requests/minute for this organization."
}
Fix: Wait the number of seconds in the Retry-After header, then retry. See Rate Limiting.
Server Error
Status code: 500
{
"detail": "An internal server error occurred."
}
Fix: Wait a moment and retry. If persistent, contact support.
Rate Limiting
Requests are rate limited per organization. REST API calls (/v1, /v2) and MCP server calls (/mcp/) share one budget, so traffic from either one counts toward the same limit. On the MCP server, protocol requests such as initialize and tools/list count too, not only tool calls.
Limits are set per organization. If you need a higher limit, contact support.
When your organization goes over its limit, the API returns 429 Too Many Requests with a Retry-After header that gives the number of seconds to wait before retrying.
REST API (/v1, /v2) response body:
{
"detail": "Rate limit exceeded: <limit> requests/minute for this organization."
}
MCP server (/mcp/) response body:
{
"error": "rate_limit_exceeded",
"error_description": "Rate limit exceeded: <limit> requests/minute for this organization."
}
MCP cursor pagination has its own, separate cap on follow-up pages. When you hit it, the tool result contains a pagination_rate_limit error; it is not an HTTP 429. See MCP Tool Pagination.
Best Practices
Honor Retry-After:
import time
import requests
def get_with_retry(url, headers, params=None, max_retries=3):
for attempt in range(max_retries + 1):
response = requests.get(url, headers=headers, params=params, timeout=30)
if response.status_code != 429 or attempt == max_retries:
return response
# Retry-After is the number of seconds to wait before retrying.
wait = int(response.headers.get("Retry-After", 2 ** attempt))
time.sleep(wait)
response = get_with_retry(
"https://api.cannmenus.com/v2/products",
headers={"X-Token": "YOUR_API_TOKEN"},
params={"states": "New Jersey", "page": 1},
)
Make fewer requests:
- Use larger pages for bulk pulls:
/v1/productsand/v2/productsacceptper_pageup to 200 (see Page Size). - Cache responses you reuse instead of re-fetching them on each page view.
- Spread large batch jobs out over time instead of sending requests in bursts.
Handling Errors in Code
Python
import requests
def get_products(params):
response = requests.get(
"https://api.cannmenus.com/v2/products",
headers={"X-Token": "YOUR_API_TOKEN"},
params=params
)
if response.status_code == 200:
return response.json()
error = response.json()
if response.status_code == 422:
raise ValueError(f"Validation error: {error['detail']}")
elif response.status_code == 403:
raise PermissionError(f"Missing API token: {error['detail']}")
elif response.status_code == 404:
raise PermissionError(f"Invalid token or not found: {error['detail']}")
elif response.status_code == 429:
retry_after = response.headers.get("Retry-After")
raise RuntimeError(f"Rate limited, retry after {retry_after}s: {error['detail']}")
else:
raise Exception(f"API error ({response.status_code}): {error['detail']}")
JavaScript
async function getProducts(params) {
const queryString = new URLSearchParams(params).toString();
const response = await fetch(
`https://api.cannmenus.com/v2/products?${queryString}`,
{ headers: { "X-Token": "YOUR_API_TOKEN" } }
);
const data = await response.json();
if (!response.ok) {
throw new Error(`API error (${response.status}): ${data.detail}`);
}
return data;
}
// Usage
try {
const products = await getProducts({ states: "California", page: 1 });
console.log(products);
} catch (error) {
console.error("API Error:", error.message);
}
Troubleshooting Checklist
| Issue | Check |
|---|---|
| 403 Forbidden | Is the X-Token header present and non-empty? |
| 404 Not Found | Is the token valid and not expired? Is the endpoint URL correct? |
| 422 Unprocessable Entity | Are required parameters (states) included and valid? |
| Empty results | Are filter values valid? Check Categories, Tags |
| 429 Rate Limited | Wait Retry-After seconds before retrying. Use larger pages (per_page) and caching to make fewer requests |
| 500 Server Error | Retry after a few seconds; contact support if persistent |
Getting Help
If you're stuck, contact support with:
- The full error response
- The request URL and parameters (without your API token)
- When the issue started
- Steps you've already tried
