Error Handling

The CannMenus API uses standard HTTP status codes and returns detailed error messages to help you diagnose issues quickly.


HTTP Status Codes

CodeMeaningDescription
200SuccessRequest completed successfully
403ForbiddenNo API token was sent
404Not FoundInvalid or expired API token, or the endpoint/resource doesn't exist
422Unprocessable EntityValidation error — missing or invalid parameters
429Too Many RequestsYour organization exceeded its rate limit. Wait the number of seconds in the Retry-After header, then retry
500Server ErrorSomething 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"
}
FieldDescription
detailHuman-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."
}

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/products and /v2/products accept per_page up 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

IssueCheck
403 ForbiddenIs the X-Token header present and non-empty?
404 Not FoundIs the token valid and not expired? Is the endpoint URL correct?
422 Unprocessable EntityAre required parameters (states) included and valid?
Empty resultsAre filter values valid? Check Categories, Tags
429 Rate LimitedWait Retry-After seconds before retrying. Use larger pages (per_page) and caching to make fewer requests
500 Server ErrorRetry after a few seconds; contact support if persistent

Getting Help

If you're stuck, contact support with:

  1. The full error response
  2. The request URL and parameters (without your API token)
  3. When the issue started
  4. Steps you've already tried